Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix dynamic field value issues #16294

Closed
wants to merge 15 commits into from
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Nodes;
using Json.More;

namespace OrchardCore.Json.Dynamic;
public class DefaultJsonDyanmicValueHandler : IJsonDynamicValueHandler
{
public bool GetValue(JsonObject jsonObject, Dictionary<string, object> dynamicValueDict, string memberName, JsonNode currentNode)
{
if (currentNode is JsonValue jsonValue)
{
var valueKind = jsonValue.GetValueKind();
switch (valueKind)
{
case JsonValueKind.String:
if (memberName == "Value")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I remember correctly the only two types of fields that currently take values from strings in OC seem to be these two types, which are always wrapped in Value

{
if (jsonValue.TryGetValue<DateTime>(out var datetime))
{
dynamicValueDict[memberName] = datetime;
return true;
}

if (jsonValue.TryGetValue<TimeSpan>(out var timeSpan))
{
dynamicValueDict[memberName] = timeSpan;
return true;
}
}
dynamicValueDict[memberName] = jsonValue.GetString();
return true;
case JsonValueKind.Number:
dynamicValueDict[memberName] = jsonValue.GetNumber();
return true;
case JsonValueKind.True:
dynamicValueDict[memberName] = true;
return true;
case JsonValueKind.False:
dynamicValueDict[memberName] = false;
return true;
}
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.Text.Json.Dynamic;
using System.Text.Json.Nodes;

namespace OrchardCore.Json.Dynamic;
public interface IJsonDynamicValueHandler
{
/// <summary>
/// Building dynamic fetch logic for the ContentItem.Content property.
/// <para><see cref="JsonDynamicObject.GetValue(string)"/></para>
/// <para><seealso cref="DefaultJsonDyanmicValueHandler"/></para>
/// </summary>
/// <param name="parentNode"></param>
/// <param name="dynamicValueDict"></param>
/// <param name="memberName"></param>
/// <param name="memberNode"></param>
/// <returns></returns>
bool GetValue(JsonObject parentNode, Dictionary<string, object?> dynamicValueDict, string memberName, JsonNode memberNode);

Check failure on line 18 in src/OrchardCore/OrchardCore.Abstractions/Json/Dynamic/IJsonDynamicValueHandler.cs

View workflow job for this annotation

GitHub Actions / Build & Test (ubuntu-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check failure on line 18 in src/OrchardCore/OrchardCore.Abstractions/Json/Dynamic/IJsonDynamicValueHandler.cs

View workflow job for this annotation

GitHub Actions / Build & Test (ubuntu-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check failure on line 18 in src/OrchardCore/OrchardCore.Abstractions/Json/Dynamic/IJsonDynamicValueHandler.cs

View workflow job for this annotation

GitHub Actions / Build & Test (windows-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check failure on line 18 in src/OrchardCore/OrchardCore.Abstractions/Json/Dynamic/IJsonDynamicValueHandler.cs

View workflow job for this annotation

GitHub Actions / Build & Test (windows-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using System.Collections.Generic;

namespace OrchardCore.Json.Dynamic;
public static class JsonDynamicConfigurations
{
public static HashSet<IJsonDynamicValueHandler> ValueHandlers { get; } = [new DefaultJsonDyanmicValueHandler()];
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Reflection;
using System.Text.Json.Nodes;
using System.Text.Json.Settings;
using Json.More;
using OrchardCore.Json.Dynamic;

#nullable enable

Expand Down Expand Up @@ -52,7 +54,6 @@ public override bool TryGetMember(GetMemberBinder binder, out object? result)
result = "{null}";
return true;
}

result = GetValue(binder.Name);
return true;
}
Expand Down Expand Up @@ -80,6 +81,7 @@ public bool Remove(string key)

public object? GetValue(string key)
{

if (_dictionary.TryGetValue(key, out var value))
{
return value;
Expand All @@ -95,6 +97,14 @@ public bool Remove(string key)
return null;
}

foreach (var handler in JsonDynamicConfigurations.ValueHandlers)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a static collection to handle dynamic values for custom development environments

{
if (handler.GetValue(_jsonObject, _dictionary, key, jsonNode))
{
return _dictionary[key];
}
}

if (jsonNode is JsonObject jsonObject)
{
return _dictionary[key] = new JsonDynamicObject(jsonObject);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.Json;
using OrchardCore.Json.Dynamic;

namespace System.Text.Json.Serialization;

Expand All @@ -21,4 +23,19 @@ public static IServiceCollection AddJsonDerivedTypeInfo<TDerived, TBase>(this IS

derivedTypes.Add(new JsonDerivedTypeInfo<TDerived, TBase>());
});

/// <summary>
/// Add additional dynamic object fetching logic.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddJsonDynamicValueHandler<T>(this IServiceCollection services) where T : IJsonDynamicValueHandler, new()
Copy link
Contributor Author

@hyzx86 hyzx86 Jun 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Custom fetch logic can be added here

{
if (JsonDynamicConfigurations.ValueHandlers.Any(x => x.GetType() != typeof(T)))
{
JsonDynamicConfigurations.ValueHandlers.Add(new T());
}
return services;
}
}
44 changes: 44 additions & 0 deletions test/OrchardCore.Tests/Data/JsonDynamicTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
using System.Dynamic;
using System.Text.Json;
using System.Text.Json.Dynamic;
using System.Text.Json.Nodes;
using OrchardCore.ContentFields.Fields;
using OrchardCore.ContentManagement;

namespace OrchardCore.Tests.Data;

Expand Down Expand Up @@ -337,4 +341,44 @@ public void JsonDynamicValueMustConvertToUri()

Assert.Equal(expectedValue, (Uri)myDynamic);
}

[Fact]
public void TestDynamicValueAssignToOther()
{
// Arrange
var contentItem = new ContentItem();
contentItem.Alter<TestPart>(part =>
{
part.TextFeildProp = new TextField { Text = "test" };
part.NumericFieldProp = new NumericField { Value = 123 };
part.BooleanFieldProp = new BooleanField { Value = true };
part.TimeFieldProp = new TimeField { Value = new TimeSpan(12, 12, 23) };
part.DateTimeFieldProp = new DateTimeField { Value = new DateTime(2024, 06, 12, 0, 43, 00) };
});

// Act
dynamic expandoValue = new ExpandoObject();
expandoValue.stringValue = contentItem.Content.TestPart.TextFeildProp.Text;
expandoValue.numberValue = contentItem.Content.TestPart.NumericFieldProp.Value;
expandoValue.booleanValue = contentItem.Content.TestPart.BooleanFieldProp.Value;
expandoValue.timeValue = contentItem.Content.TestPart.TimeFieldProp.Value;
expandoValue.dateTimeValue = contentItem.Content.TestPart.DateTimeFieldProp.Value;

// Assert
var jsonAsString = JConvert.SerializeObject((ExpandoObject)expandoValue);
Assert.Equal("{\"stringValue\":\"test\",\"numberValue\":123,\"booleanValue\":true,\"timeValue\":\"12:12:23\",\"dateTimeValue\":\"2024-06-12T00:43:00Z\"}", jsonAsString);
}

public class TestPart : ContentPart
{
public TextField TextFeildProp { get; set; }

public NumericField NumericFieldProp { get; set; }

public BooleanField BooleanFieldProp { get; set; }

public TimeField TimeFieldProp { get; set; }

public DateTimeField DateTimeFieldProp { get; set; }
}
}
Loading