Skip to content

Commit

Permalink
fix for stringify array replacer #725
Browse files Browse the repository at this point in the history
- Per spec, Numbers in an array replacer should be converted to strings
  prior to adding to the property list
- Duplicates should be removed during construction of the property list
- Changed some variables declared as List  to Collection instead because
  that is sufficient for how they are being used
- Moved List to array conversion from happening in every iteration of
  the jo method to being called once in the stringify method
- Convert strings items representing integers to their Integer value for
  correct property lookup
  • Loading branch information
tonygermano authored and gbrail committed May 5, 2021
1 parent 9638cf1 commit 8e8c27c
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 12 deletions.
38 changes: 26 additions & 12 deletions src/org/mozilla/javascript/NativeJSON.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.mozilla.javascript.json.JsonParser;
Expand Down Expand Up @@ -200,7 +200,7 @@ private static class StringifyState {
String indent,
String gap,
Callable replacer,
List<Object> propertyList) {
Object[] propertyList) {
this.cx = cx;
this.scope = scope;

Expand All @@ -214,7 +214,7 @@ private static class StringifyState {
String indent;
String gap;
Callable replacer;
List<Object> propertyList;
Object[] propertyList;

Context cx;
Scriptable scope;
Expand All @@ -225,22 +225,36 @@ public static Object stringify(
String indent = "";
String gap = "";

List<Object> propertyList = null;
Object[] propertyList = null;
Callable replacerFunction = null;

if (replacer instanceof Callable) {
replacerFunction = (Callable) replacer;
} else if (replacer instanceof NativeArray) {
propertyList = new LinkedList<Object>();
LinkedHashSet<Object> propertySet = new LinkedHashSet<Object>();
NativeArray replacerArray = (NativeArray) replacer;
for (int i : replacerArray.getIndexIds()) {
Object v = replacerArray.get(i, replacerArray);
if (v instanceof String || v instanceof Number) {
propertyList.add(v);
} else if (v instanceof NativeString || v instanceof NativeNumber) {
propertyList.add(ScriptRuntime.toString(v));
if (v instanceof String) {
propertySet.add(v);
} else if (v instanceof Number
|| v instanceof NativeString
|| v instanceof NativeNumber) {
// TODO: This should also apply to subclasses of NativeString and NativeNumber
// once the class, extends, and super keywords are implemented
propertySet.add(ScriptRuntime.toString(v));
}
}
// After items have been converted to strings and duplicates removed, transform to an
// array and convert indexed keys back to Integers as required for later processing
propertyList = new Object[propertySet.size()];
int i = 0;
for (Object prop : propertySet) {
ScriptRuntime.StringIdOrIndex idOrIndex = ScriptRuntime.toStringIdOrIndex(cx, prop);
// This will always be a String or Integer
propertyList[i++] =
(idOrIndex.stringId == null) ? idOrIndex.index : idOrIndex.stringId;
}
}

if (space instanceof NativeNumber) {
Expand Down Expand Up @@ -413,12 +427,12 @@ private static String jo(Scriptable value, StringifyState state) {
state.indent = state.indent + state.gap;
Object[] k = null;
if (state.propertyList != null) {
k = state.propertyList.toArray();
k = state.propertyList;
} else {
k = value.getIds();
}

List<Object> partial = new LinkedList<Object>();
Collection<Object> partial = new LinkedList<Object>();

for (Object p : k) {
Object strP = str(p, value, state);
Expand Down Expand Up @@ -463,7 +477,7 @@ private static String ja(Scriptable value, StringifyState state) {

String stepback = state.indent;
state.indent = state.indent + state.gap;
List<Object> partial = new LinkedList<Object>();
Collection<Object> partial = new LinkedList<Object>();

if (unwrapped != null) {
Object[] elements = null;
Expand Down
28 changes: 28 additions & 0 deletions testsrc/jstests/json-stringify-array-replacer.jstest
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// This test is a stand-in for the following until they are run by the Test262SuiteTest.
// https://github.com/tc39/test262/blob/70bc32edab22b44db9d671ce505db8842ae200b6/test/built-ins/JSON/stringify/replacer-array-duplicates.js
// https://github.com/tc39/test262/blob/70bc32edab22b44db9d671ce505db8842ae200b6/test/built-ins/JSON/stringify/replacer-array-number.js


function assertEqual(actual, expected) {
if (actual !== expected) throw 'expected: <' + expected + '> but found <' + actual + '>';
}

var obj = {"0":1, "1":2, "2":3, "name":4};
var actual = JSON.stringify(obj, ["0", "1", "name", "name"]);
var expected = JSON.stringify({"0":1, "1":2, "name":4});
assertEqual(expected, actual);

var obj = {"2":"b","3":"c","1":"a"};
var expected = JSON.stringify({"1":"a","2":"b","3":"c"});

var actual = JSON.stringify(obj, ["1","2","3"]);
assertEqual(expected, actual);

var actual = JSON.stringify(obj, [1,2,3]);
assertEqual(expected, actual);

"success"

0 comments on commit 8e8c27c

Please sign in to comment.