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

PropertyNamingPolicy != CamelCase prevents script execution #199

Open
criton2000 opened this issue Sep 4, 2024 · 4 comments
Open

PropertyNamingPolicy != CamelCase prevents script execution #199

criton2000 opened this issue Sep 4, 2024 · 4 comments

Comments

@criton2000
Copy link

criton2000 commented Sep 4, 2024

Hello, thank you for your excellent library.
I encountered an issue (I searched for an issue on this argument, but I did not find anything, forgive me if my failure)
It seems that configuring JsonService to serialize object properties with a rule different from CamelCase, javascript module cannot be executed.
Executing following code (substantially static API example with JsonService settings) throws a Jering.Javascript.NodeJS.InvocationException: Invalid module source type: undefined.
If PropertyNamingPolicy == JsonNamingPolicy.CamelCase it works regularily, with other values for PropertyNamingPolicy (SnakeCase* or KebabCase*) it throws Jering.Javascript.NodeJS.InvocationException: Unexpected error
Jering library version 7.0.0, .NET 8, nodeJs 20.10.0

using Jering.Javascript.NodeJS;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Text.Encodings.Web;
using System.Text.Json.Serialization;
using System.Text.Json;

namespace testJeringNodeMinimal
{
    public class MyJsonService : IJsonService
    {
        private static readonly JsonSerializerOptions _jsonSerializerOptions = new()
        {
            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
            PropertyNamingPolicy = null, //JsonNamingPolicy.CamelCase,
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
            PropertyNameCaseInsensitive = true
        };

        public static JsonSerializerOptions SerializerOptions => _jsonSerializerOptions;
        /// <inheritdoc />
        public ValueTask<T?> DeserializeAsync<T>(Stream stream, CancellationToken cancellationToken = default)
        {
            return JsonSerializer.DeserializeAsync<T>(stream, _jsonSerializerOptions, cancellationToken);
        }

        /// <inheritdoc />
        public Task SerializeAsync<T>(Stream stream, T value, CancellationToken cancellationToken = default)
        {
            return JsonSerializer.SerializeAsync(stream, value, _jsonSerializerOptions, cancellationToken);
        }
    }

    internal class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
            var services = new ServiceCollection();
            services.AddLogging(loggingBuilder =>
            {
                loggingBuilder.SetMinimumLevel(LogLevel.Debug);
                loggingBuilder.AddConsole();
            });
            services.AddNodeJS();

            // Overwrite the DI service
            services.AddSingleton<IJsonService, MyJsonService>();

            StaticNodeJSService.SetServices(services);
            {
                try
                {
                    string javascriptModule = @"
module.exports = (callback, x, y) => {  // Module must export a function that takes a callback as its first parameter
    var result = x + y; // Your javascript logic
    callback(null /* If an error occurred, provide an error object or message */, result); // Call the callback when you're done.
}";

                    // Invoke javascript
                    int result = await StaticNodeJSService.InvokeFromStringAsync<int>(javascriptModule, args: new object[] { 3, 5 });
                    Console.WriteLine(result);

                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                    throw;
                }
            }
        }
    }
}

@criton2000
Copy link
Author

Analyzing with Wireshark, I saw that POST request contains moduleSourceType, moduleSource and args fields when PropertyNamingPolicy == CamelCase, and it works.
If PropertyNamingPolicy changes, those field names are modified according with requested rule (for example module-source-type or ModuleSourceType), and request fails.
Probably it could be sufficient to force InvocationRequest properties serialization with JsonPropertyName attributes

@runxc1
Copy link

runxc1 commented Sep 19, 2024

Seems I am running into the same issue. Did you find a solution? I'm currently wondering if I can serialize the object and pass it in as a string to work around this issue.

@criton2000
Copy link
Author

criton2000 commented Sep 20, 2024

I did explicit conversion using a DefaultJsonTypeInfoResolver, adding a Modifier, and in its callback I wrote something like

        public static void CallbackModifier(JsonTypeInfo jsonTypeInfo)
        {
            if (jsonTypeInfo.Kind != JsonTypeInfoKind.Object)
                return;

            // waiting resolution https://github.com/JeringTech/Javascript.NodeJS/issues/199
            // apply only to IBase implementing objects
            if (jsonTypeInfo.Type.IsAssignableTo(typeof(IBase)))
            {
                foreach (var propertyInfo in jsonTypeInfo.Type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
                {
                    //Console.WriteLine(propertyInfo.Name);

                    var oldProp
                        = jsonTypeInfo.Properties.FirstOrDefault(p => string.Compare(p.Name, propertyInfo.Name, true, System.Globalization.CultureInfo.InvariantCulture) == 0);
                    if (oldProp != null)
                    {
                        oldProp.Name = propertyInfo.Name;
                    } else
                    {
                        JsonPropertyInfo jsonPropertyInfo = jsonTypeInfo.CreateJsonPropertyInfo(propertyInfo.PropertyType, propertyInfo.Name);
                        if (propertyInfo.CanWrite)
                        {
                            jsonPropertyInfo.Set = propertyInfo.SetValue;
                        }
                        if (propertyInfo.CanRead)
                        {
                            jsonPropertyInfo.Get = propertyInfo.GetValue;
                        }
                        jsonTypeInfo.Properties.Add(jsonPropertyInfo);
                    }
                }
            }

        }

because I need it only for certain objects. Perhaps it could be possible to hack library field names, but I did not think about this possibility until now.

@runxc1
Copy link

runxc1 commented Sep 20, 2024

I might need to look into that. For now I just went ahead and passed in a string bypassing all of the serialization and then calling JSON.parse on the other side as the object going in is complex but I just need rendered html returned as a string.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants