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 JSON serialization logic to adhere to latest specification #820

Merged
merged 2 commits into from
Dec 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions Jint.Tests.Test262/BuiltIns/JSONTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Xunit;

namespace Jint.Tests.Test262.BuiltIns
{
public class JSONTests : Test262Test
{
[Theory(DisplayName = "built-ins\\JSON")]
[MemberData(nameof(SourceFiles), "built-ins\\JSON", false)]
[MemberData(nameof(SourceFiles), "built-ins\\JSON", true, Skip = "Skipped")]
protected void RunTest(SourceFile sourceFile)
{
RunTestInternal(sourceFile);
}
}
}
107 changes: 47 additions & 60 deletions Jint/Native/Json/JsonInstance.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Jint.Collections;
using Jint.Native.Object;
using Jint.Native.Symbol;
using Jint.Runtime;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Interop;
Expand All @@ -8,8 +9,6 @@ namespace Jint.Native.Json
{
public sealed class JsonInstance : ObjectInstance
{
private JsValue _reviver;

private JsonInstance(Engine engine)
: base(engine, objectClass: ObjectClass.JSON)
{
Expand All @@ -32,107 +31,95 @@ protected override void Initialize()
["stringify"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "stringify", Stringify, 3), true, false, true)
};
SetProperties(properties);

var symbols = new SymbolDictionary(1)
{
[GlobalSymbolRegistry.ToStringTag] = new PropertyDescriptor("JSON", false, false, true),
};
SetSymbols(symbols);
}

private JsValue AbstractWalkOperation(ObjectInstance thisObject, JsValue prop)
private static JsValue InternalizeJSONProperty(JsValue holder, JsValue name, ICallable reviver)
{
JsValue value = thisObject.Get(prop, thisObject);
if (value.IsObject())
JsValue temp = holder.Get(name, holder);
if (temp is ObjectInstance val)
{
var valueAsObject = value.AsObject();
if (valueAsObject.Class == ObjectClass.Array)
if (val.IsArray())
{
var valAsArray = value.AsArray();
var i = 0;
var arrLen = valAsArray.GetLength();
while (i < arrLen)
var i = 0UL;
var len = TypeConverter.ToLength(val.Get(CommonProperties.Length));
while (i < len)
{
var newValue = AbstractWalkOperation(valAsArray, JsString.Create(i));
if (newValue.IsUndefined())
var prop = JsString.Create(i);
var newElement = InternalizeJSONProperty(val, prop, reviver);
if (newElement.IsUndefined())
{
valAsArray.Delete(JsString.Create(i));
val.Delete(prop);
}
else
{
valAsArray.DefineOwnProperty
(
JsString.Create(i),
new PropertyDescriptor
(
value: newValue,
PropertyFlag.ConfigurableEnumerableWritable
));
val.CreateDataProperty(prop, newElement);
}
i = i + 1;
}
}
else
{
var keys = valueAsObject.GetOwnProperties();
var keys = val.EnumerableOwnPropertyNames(EnumerableOwnPropertyNamesKind.Key);
foreach (var p in keys)
{
var newElement = AbstractWalkOperation(valueAsObject, p.Key);
var newElement = InternalizeJSONProperty(val, p, reviver);
if (newElement.IsUndefined())
{
valueAsObject.Delete(p.Key);
val.Delete(p);
}
else
{
valueAsObject.DefineOwnProperty(
p.Key,
new PropertyDescriptor
(
value: newElement,
PropertyFlag.ConfigurableEnumerableWritable
));
val.CreateDataProperty(p, newElement);
}
}
}
}
return _reviver.Invoke(thisObject, new[] { prop, value });

return reviver.Call(holder, new[] { name, temp });
}

/// <summary>
/// https://tc39.es/ecma262/#sec-json.parse
/// </summary>
public JsValue Parse(JsValue thisObject, JsValue[] arguments)
{
var jsonString = TypeConverter.ToString(arguments.At(0));
var reviver = arguments.At(1);

var parser = new JsonParser(_engine);
var res = parser.Parse(TypeConverter.ToString(arguments[0]));
if (arguments.Length > 1)
var unfiltered = parser.Parse(jsonString);

if (reviver.IsCallable)
{
_reviver = arguments[1];
ObjectInstance revRes = _engine.Object.Construct(Arguments.Empty);
revRes.SetProperty("", new PropertyDescriptor(value: res, PropertyFlag.ConfigurableEnumerableWritable));
return AbstractWalkOperation(revRes, JsString.Empty);
var root = _engine.Object.Construct(Arguments.Empty);
var rootName = JsString.Empty;
root.CreateDataPropertyOrThrow(rootName, unfiltered);
return InternalizeJSONProperty(root, rootName, (ICallable) reviver);
}
else
{
return unfiltered;
}
return res;
}

public JsValue Stringify(JsValue thisObject, JsValue[] arguments)
{
JsValue
value = Undefined,
replacer = Undefined,
space = Undefined;
var value = arguments.At(0);
var replacer = arguments.At(1);
var space = arguments.At(2);

if (arguments.Length > 2)
if (value.IsUndefined() && replacer.IsUndefined())
{
space = arguments[2];
}

if (arguments.Length > 1)
{
replacer = arguments[1];
}

if (arguments.Length > 0)
{
value = arguments[0];
}

var serializer = new JsonSerializer(_engine);
if (value.IsUndefined() && replacer.IsUndefined()) {
return Undefined;
}

var serializer = new JsonSerializer(_engine);
return serializer.Serialize(value, replacer, space);
}
}
Expand Down
19 changes: 7 additions & 12 deletions Jint/Native/Json/JsonSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public JsValue Serialize(JsValue value, JsValue replacer, JsValue space)

// for JSON.stringify(), any function passed as the first argument will return undefined
// if the replacer is not defined. The function is not called either.
if (value is ICallable callable && ReferenceEquals(replacer, Undefined.Instance))
if (value.IsCallable && ReferenceEquals(replacer, Undefined.Instance))
{
return Undefined.Instance;
}
Expand Down Expand Up @@ -119,9 +119,8 @@ public JsValue Serialize(JsValue value, JsValue replacer, JsValue space)
return Str(JsString.Empty, wrapper);
}

private JsValue Str(JsValue key, ObjectInstance holder)
private JsValue Str(JsValue key, JsValue holder)
{

var value = holder.Get(key, holder);
if (value.IsObject())
{
Expand All @@ -141,7 +140,6 @@ private JsValue Str(JsValue key, ObjectInstance holder)
value = replacerFunctionCallable.Call(holder, Arguments.From(key, value));
}


if (value.IsObject())
{
var valueObj = value.AsObject();
Expand All @@ -157,7 +155,7 @@ private JsValue Str(JsValue key, ObjectInstance holder)
value = TypeConverter.ToPrimitive(value);
break;
case ObjectClass.Array:
value = SerializeArray(value.As<ArrayInstance>());
value = SerializeArray(value);
return value;
case ObjectClass.Object:
value = SerializeObject(value.AsObject());
Expand Down Expand Up @@ -195,12 +193,9 @@ private JsValue Str(JsValue key, ObjectInstance holder)

if (value.IsObject() && isCallable == false)
{
if (value.AsObject().Class == ObjectClass.Array)
{
return SerializeArray(value.As<ArrayInstance>());
}

return SerializeObject(value.AsObject());
return value.AsObject().Class == ObjectClass.Array
? SerializeArray(value)
: SerializeObject(value.AsObject());
}

return JsValue.Undefined;
Expand Down Expand Up @@ -251,7 +246,7 @@ private static string Quote(string value)
return sb.ToString();
}

private string SerializeArray(ArrayInstance value)
private string SerializeArray(JsValue value)
{
EnsureNonCyclicity(value);
_stack.Push(value);
Expand Down
2 changes: 1 addition & 1 deletion Jint/Native/Object/ObjectInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1216,7 +1216,7 @@ internal static ICallable GetMethod(Engine engine, JsValue v, JsValue p)
}
}

internal JsValue EnumerableOwnPropertyNames(EnumerableOwnPropertyNamesKind kind)
internal ArrayInstance EnumerableOwnPropertyNames(EnumerableOwnPropertyNamesKind kind)
{
var ownKeys = GetOwnPropertyKeys(Types.String);

Expand Down