Skip to content

Commit

Permalink
Exclude static members from ObjectWrapper by default
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma committed Oct 20, 2024
1 parent 7857b9c commit b032c22
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 31 deletions.
56 changes: 33 additions & 23 deletions Jint.Tests.PublicInterface/InteropTests.Json.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Dynamic;
using FluentAssertions;
using Jint.Runtime.Interop;

namespace Jint.Tests.PublicInterface;
Expand Down Expand Up @@ -102,31 +103,27 @@ public void CanStringifyTimeSpanUsingCustomToJsonHook()

Assert.Equal(expected, value);
}

[Fact]
public void CanStringifyUsingSerializeToJson()
{
object testObject = new { Foo = "bar", FooBar = new { Foo = 123.45, Foobar = new DateTime(2022, 7, 16, 0, 0, 0, DateTimeKind.Utc) } };

// without interop

var engineNoInterop = new Engine();
engineNoInterop.SetValue("TimeSpan", TypeReference.CreateTypeReference<TimeSpan>(engineNoInterop));
Assert.Throws<Jint.Runtime.JavaScriptException>(
() => engineNoInterop.Evaluate("JSON.stringify(TimeSpan.FromSeconds(3))"));

engineNoInterop.SetValue("TestObject", testObject);
Assert.Equal(
"{\"Foo\":\"bar\",\"FooBar\":{\"Foo\":123.45,\"Foobar\":\"2022-07-16T00:00:00.000Z\"}}",
engineNoInterop.Evaluate("JSON.stringify(TestObject)"));


var e = new Engine();
e.SetValue("TimeSpan", typeof(TimeSpan));
#if NETFRAMEWORK
e.Evaluate("JSON.stringify(TimeSpan.FromSeconds(3))").AsString().Should().Be("""{"Ticks":30000000,"Days":0,"Hours":0,"Milliseconds":0,"Minutes":0,"Seconds":3,"TotalDays":0.00003472222222222222,"TotalHours":0.0008333333333333333,"TotalMilliseconds":3000,"TotalMinutes":0.05,"TotalSeconds":3}""");
#else
e.Evaluate("JSON.stringify(TimeSpan.FromSeconds(3))").AsString().Should().Be("""{"Ticks":30000000,"Days":0,"Hours":0,"Milliseconds":0,"Microseconds":0,"Nanoseconds":0,"Minutes":0,"Seconds":3,"TotalDays":0.00003472222222222222,"TotalHours":0.0008333333333333334,"TotalMilliseconds":3000,"TotalMicroseconds":3000000,"TotalNanoseconds":3000000000,"TotalMinutes":0.05,"TotalSeconds":3}""");
#endif

e.SetValue("TestObject", testObject);
e.Evaluate("JSON.stringify(TestObject)").AsString().Should().Be("""{"Foo":"bar","FooBar":{"Foo":123.45,"Foobar":"2022-07-16T00:00:00.000Z"}}""");

// interop using Newtonsoft serializer, for example with snake case naming

string Serialize(object o) =>
Newtonsoft.Json.JsonConvert.SerializeObject(o,
new Newtonsoft.Json.JsonSerializerSettings {
ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver {
NamingStrategy = new Newtonsoft.Json.Serialization.SnakeCaseNamingStrategy() } });

var engine = new Engine(options =>
{
options.Interop.SerializeToJson = Serialize;
Expand All @@ -136,13 +133,26 @@ string Serialize(object o) =>

var expected = Serialize(TimeSpan.FromSeconds(3));
var actual = engine.Evaluate("JSON.stringify(TimeSpan.FromSeconds(3));");
Assert.Equal(expected, actual);
actual.AsString().Should().Be(expected);

expected = Serialize(testObject);
actual = engine.Evaluate("JSON.stringify(TestObject)");
Assert.Equal(expected, actual);
actual.AsString().Should().Be(expected);

actual = engine.Evaluate("JSON.stringify({ nestedValue: TestObject })");
Assert.Equal($@"{{""nestedValue"":{expected}}}", actual);
actual.AsString().Should().Be($$"""{"nestedValue":{{expected}}}""");
return;

string Serialize(object o)
{
var settings = new Newtonsoft.Json.JsonSerializerSettings
{
ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver
{
NamingStrategy = new Newtonsoft.Json.Serialization.SnakeCaseNamingStrategy()
}
};
return Newtonsoft.Json.JsonConvert.SerializeObject(o, settings);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

<ItemGroup>
<PackageReference Include="Acornima.Extras" />
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Flurl.Http.Signed" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="MongoDB.Bson.signed" />
Expand Down
9 changes: 6 additions & 3 deletions Jint.Tests/Runtime/InteropTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public class Bar
[Fact]
public void ShouldStringifyNetObjects()
{
_engine.SetValue("foo", new Foo());
_engine.SetValue("foo", typeof(Foo));
var json = _engine.Evaluate("JSON.stringify(foo.GetBar())").AsString();
Assert.Equal("{\"Test\":\"123\"}", json);
}
Expand Down Expand Up @@ -2781,17 +2781,20 @@ static IEnumerable<string> MemberNameCreator(MemberInfo prop)
options.SetTypeResolver(customTypeResolver);
options.AddExtensionMethods(typeof(CustomNamedExtensions));
});

engine.SetValue("o", new CustomNamed());
Assert.Equal("StringField", engine.Evaluate("o.jsStringField").AsString());
Assert.Equal("StringField", engine.Evaluate("o.jsStringField2").AsString());
Assert.Equal("StaticStringField", engine.Evaluate("o.jsStaticStringField").AsString());
Assert.Equal("StringProperty", engine.Evaluate("o.jsStringProperty").AsString());
Assert.Equal("Method", engine.Evaluate("o.jsMethod()").AsString());
Assert.Equal("StaticMethod", engine.Evaluate("o.jsStaticMethod()").AsString());
Assert.Equal("InterfaceStringProperty", engine.Evaluate("o.jsInterfaceStringProperty").AsString());
Assert.Equal("InterfaceMethod", engine.Evaluate("o.jsInterfaceMethod()").AsString());
Assert.Equal("ExtensionMethod", engine.Evaluate("o.jsExtensionMethod()").AsString());

engine.SetValue("CustomNamed", typeof(CustomNamed));
Assert.Equal("StaticStringField", engine.Evaluate("CustomNamed.jsStaticStringField").AsString());
Assert.Equal("StaticMethod", engine.Evaluate("CustomNamed.jsStaticMethod()").AsString());

engine.SetValue("XmlHttpRequest", typeof(CustomNamedEnum));
engine.Evaluate("o.jsEnumProperty = XmlHttpRequest.HEADERS_RECEIVED;");
Assert.Equal((int) CustomNamedEnum.HeadersReceived, engine.Evaluate("o.jsEnumProperty").AsNumber());
Expand Down
5 changes: 5 additions & 0 deletions Jint/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,11 @@ public class InteropOptions
/// All other values are ignored.
/// </summary>
public MemberTypes ObjectWrapperReportedMemberTypes { get; set; } = MemberTypes.Field | MemberTypes.Property | MemberTypes.Method;

/// <summary>
/// Reported member binding flags when reflecting, defaults to <see cref="BindingFlags.Instance" /> | <see cref="BindingFlags.Public" />.
/// </summary>
public BindingFlags ObjectWrapperReportedBindingFlags { get; set; } = BindingFlags.Instance | BindingFlags.Public;
}

public class ConstraintOptions
Expand Down
5 changes: 2 additions & 3 deletions Jint/Runtime/Interop/ObjectWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,9 +255,8 @@ private IEnumerable<JsValue> EnumerateOwnPropertyKeys(Types types)
{
var interopOptions = _engine.Options.Interop;

// we take public properties, fields and methods
var bindingFlags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public;

// we take properties, fields and methods
var bindingFlags = interopOptions.ObjectWrapperReportedBindingFlags;
if ((interopOptions.ObjectWrapperReportedMemberTypes & MemberTypes.Property) == MemberTypes.Property)
{
foreach (var p in ClrType.GetProperties(bindingFlags))
Expand Down
4 changes: 2 additions & 2 deletions Jint/Runtime/Interop/TypeResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,11 @@ private ReflectionAccessor ResolvePropertyDescriptorFactory(
// we can always check indexer if there's one, and then fall back to properties if indexer returns null
IndexerAccessor.TryFindIndexer(engine, type, memberName, out var indexerAccessor, out var indexer);

const BindingFlags BindingFlags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public;
var bindingFlags = engine.Options.Interop.ObjectWrapperReportedBindingFlags;

// properties and fields cannot be numbers
if (!isInteger
&& TryFindMemberAccessor(engine, type, memberName, BindingFlags, indexer, out var temp)
&& TryFindMemberAccessor(engine, type, memberName, bindingFlags, indexer, out var temp)
&& (!mustBeReadable || temp.Readable)
&& (!mustBeWritable || temp.Writable))
{
Expand Down

0 comments on commit b032c22

Please sign in to comment.