Skip to content

Commit

Permalink
Fix JSON.stringify for lists (#1006)
Browse files Browse the repository at this point in the history
* split InteropTests a bit
* cleanup JsonSerializer a bit
  • Loading branch information
lahma authored Nov 8, 2021
1 parent dac5a0b commit b582c8b
Show file tree
Hide file tree
Showing 5 changed files with 405 additions and 305 deletions.
185 changes: 185 additions & 0 deletions Jint.Tests/Runtime/InteropTests.Dynamic.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using Xunit;

namespace Jint.Tests.Runtime
{
public partial class InteropTests
{
[Fact]
public void CanAccessExpandoObject()
{
var engine = new Engine();
dynamic expando = new ExpandoObject();
expando.Name = "test";
engine.SetValue("expando", expando);
Assert.Equal("test", engine.Evaluate("expando.Name").ToString());
}

[Fact]
public void CanAccessMemberNamedItemThroughExpando()
{
var parent = (IDictionary<string, object>) new ExpandoObject();
var child = (IDictionary<string, object>) new ExpandoObject();
var values = (IDictionary<string, object>) new ExpandoObject();

parent["child"] = child;
child["item"] = values;
values["title"] = "abc";

_engine.SetValue("parent", parent);
Assert.Equal("abc", _engine.Evaluate("parent.child.item.title"));
}


[Fact]
public void EngineShouldStringifyAnExpandoObjectCorrectly()
{
var engine = new Engine();

dynamic expando = new ExpandoObject();
expando.foo = 5;
expando.bar = "A string";
engine.SetValue(nameof(expando), expando);

var result = engine.Evaluate($"JSON.stringify({nameof(expando)})").AsString();
Assert.Equal("{\"foo\":5,\"bar\":\"A string\"}", result);
}

[Fact]
public void EngineShouldStringifyAnExpandoObjectWithValuesCorrectly()
{
// https://github.com/sebastienros/jint/issues/995
var engine = new Engine();

dynamic expando = new ExpandoObject();
expando.Values = 1;
engine.SetValue("expando", expando);

Assert.Equal("{\"Values\":1}", engine.Evaluate($"JSON.stringify(expando)").AsString());
}

[Fact]
public void ShouldForOfOnExpandoObject()
{
dynamic o = new ExpandoObject();
o.a = 1;
o.b = 2;

_engine.SetValue("dynamic", o);

var result = _engine.Evaluate("var l = ''; for (var x of dynamic) l += x; return l;").AsString();

Assert.Equal("a,1b,2", result);
}

[Fact]
public void ShouldConvertObjectInstanceToExpando()
{
_engine.Evaluate("var o = {a: 1, b: 'foo'}");
var result = _engine.GetValue("o");

dynamic value = result.ToObject();

Assert.Equal(1, value.a);
Assert.Equal("foo", value.b);

var dic = (IDictionary<string, object>) result.ToObject();

Assert.Equal(1d, dic["a"]);
Assert.Equal("foo", dic["b"]);
}

[Fact]
public void EngineShouldStringifyAnJObjectArrayWithValuesCorrectly()
{
//https://github.com/OrchardCMS/OrchardCore/issues/10648
var engine = new Engine();
var queryResults = new List<dynamic>();
queryResults.Add(new { Text = "Text1", Value = 1 });
queryResults.Add(new { Text = "Text2", Value = 2 });

engine.SetValue("testSubject", queryResults.ToArray());
var fromEngine2 = engine.Evaluate("return JSON.stringify(testSubject);");
var result2 = fromEngine2.ToString();
Assert.Equal("[{\"Text\":\"Text1\",\"Value\":1},{\"Text\":\"Text2\",\"Value\":2}]", result2);
}

[Fact]
public void EngineShouldStringifyDynamicObjectListWithValuesCorrectly()
{
var engine = new Engine();
var source = new dynamic[] { new { Text = "Text1", Value = 1 }, new { Text = "Text2", Value = 2 } };

var objects = source.ToList();
engine.SetValue("testSubject", objects);
var fromEngine = engine.Evaluate("return JSON.stringify(testSubject);");
var result = fromEngine.ToString();
Assert.Equal("[{\"Text\":\"Text1\",\"Value\":1},{\"Text\":\"Text2\",\"Value\":2}]", result);
}

[Fact]
public void EngineShouldStringifyDynamicObjectArrayWithValuesCorrectly()
{
var engine = new Engine();
var source = new dynamic[] { new { Text = "Text1", Value = 1 }, new { Text = "Text2", Value = 2 } };

engine.SetValue("testSubject", source.AsEnumerable());
var fromEngine = engine.Evaluate("return JSON.stringify(testSubject);");
var result = fromEngine.ToString();
Assert.Equal("[{\"Text\":\"Text1\",\"Value\":1},{\"Text\":\"Text2\",\"Value\":2}]", result);
}

[Fact]
public void CanAccessDynamicObject()
{
var test = new DynamicClass();
var engine = new Engine();

engine.SetValue("test", test);

Assert.Equal("a", engine.Evaluate("test.a").AsString());
Assert.Equal("b", engine.Evaluate("test.b").AsString());

engine.Evaluate("test.a = 5; test.b = 10; test.Name = 'Jint'");

Assert.Equal(5, engine.Evaluate("test.a").AsNumber());
Assert.Equal(10, engine.Evaluate("test.b").AsNumber());

Assert.Equal("Jint", engine.Evaluate("test.Name").AsString());
Assert.True(engine.Evaluate("test.ContainsKey('a')").AsBoolean());
Assert.True(engine.Evaluate("test.ContainsKey('b')").AsBoolean());
Assert.False(engine.Evaluate("test.ContainsKey('c')").AsBoolean());
}

private class DynamicClass : DynamicObject
{
private readonly Dictionary<string, object> _properties = new Dictionary<string, object>();

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = binder.Name;
if (_properties.TryGetValue(binder.Name, out var value))
{
result = value;
}

return true;
}

public override bool TrySetMember(SetMemberBinder binder, object value)
{
_properties[binder.Name] = value;
return true;
}

public string Name { get; set; }

public bool ContainsKey(string key)
{
return _properties.ContainsKey(key);
}
}
}
}
94 changes: 94 additions & 0 deletions Jint.Tests/Runtime/InteropTests.NewtonsoftJson.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Jint.Runtime;
using Jint.Runtime.Interop;
using Newtonsoft.Json.Linq;
using Xunit;

namespace Jint.Tests.Runtime
{
public partial class InteropTests
{
[Fact]
public void AccessingJObjectShouldWork()
{
var o = new JObject
{
new JProperty("name", "test-name")
};
_engine.SetValue("o", o);
Assert.True(_engine.Evaluate("return o.name == 'test-name'").AsBoolean());
}

[Fact]
public void AccessingJArrayViaIntegerIndexShouldWork()
{
var o = new JArray("item1", "item2");
_engine.SetValue("o", o);
Assert.True(_engine.Evaluate("return o[0] == 'item1'").AsBoolean());
Assert.True(_engine.Evaluate("return o[1] == 'item2'").AsBoolean());
}

[Fact]
public void DictionaryLikeShouldCheckIndexerAndFallBackToProperty()
{
const string json = @"{ ""Type"": ""Cat"" }";
var jObjectWithTypeProperty = JObject.Parse(json);

_engine.SetValue("o", jObjectWithTypeProperty);

var typeResult = _engine.Evaluate("o.Type");

// JToken requires conversion
Assert.Equal("Cat", TypeConverter.ToString(typeResult));

// weak equality does conversions from native types
Assert.True(_engine.Evaluate("o.Type == 'Cat'").AsBoolean());
}

[Fact]
public void ShouldBeAbleToIndexJObjectWithStrings()
{
var engine = new Engine();

const string json = @"
{
'Properties': {
'expirationDate': {
'Value': '2021-10-09T00:00:00Z'
}
}
}";

var obj = JObject.Parse(json);
engine.SetValue("o", obj);
var value = engine.Evaluate("o.Properties.expirationDate.Value");
var wrapper = Assert.IsAssignableFrom<ObjectWrapper>(value);
var token = wrapper.Target as JToken;
var localDateTimeString = DateTime.Parse("2021-10-09T00:00:00Z").ToUniversalTime();
Assert.Equal(localDateTimeString.ToString(), token.ToString());
}

// https://github.com/OrchardCMS/OrchardCore/issues/10648
[Fact]
public void EngineShouldStringifyAnJObjectListWithValuesCorrectly()
{
var engine = new Engine();
var queryResults = new List<dynamic>
{
new { Text = "Text1", Value = 1 },
new { Text = "Text2", Value = 2 }
};

engine.SetValue("testSubject", queryResults.Select(x => JObject.FromObject(x)));
var fromEngine = engine.Evaluate("return JSON.stringify(testSubject);");
var result = fromEngine.ToString();

// currently we do not materialize LINQ enumerables
// Assert.Equal("[{\"Text\":\"Text1\",\"Value\":1},{\"Text\":\"Text2\",\"Value\":2}]", result);

Assert.Equal("{\"Current\":null}", result);
}
}
}
Loading

0 comments on commit b582c8b

Please sign in to comment.