/*
 *  Test the property list (2nd argument) of JSON.stringify().
 */

/*===
{"foo":1,"bar":2,"baz":4}
{"baz":4,"foo":1,"bar":2}
{"foo":1,"bar":2,"baz":4}
{"baz":4,"foo":1,"bar":2}
===*/

/* If the 2nd argument to stringify() is an array, it becomes the
 * PropertyList of the serialization algorithm and affects the JO()
 * algorithm concretely.
 *
 * The specification requires that serialization of properties will:
 *
 *   (1) allow serialization of non-enumerable properties
 *   (2) will follow PropertyList order, not the object's enumeration
 *       order (order of properties in Object.keys())
 *   (3) block serializing the same property name multiple times
 *       (E5.1 Section 15.12.3, main algorithm, step 4.b.ii.5)
 *
 * E5.1 Section 15.12.3, main algorithm, step 4.b.ii.ii is tricky:
 * it requires that array indexed properties are enumerated in
 * ascending order.  This is stricter than normal enumeration
 * requirements and causes some compliance issues in the current
 * implementation when a sparse array is used.  There are separate
 * tests for this case.
 *
 * Note that at least V8 does not block serialization of the same
 * property name.  There is a separate test for this, too.
 */

function stringifyPropertyListTest1() {
    var obj = {
        "foo": 1,
        "bar": 2,
        "quux": 3
    };

    Object.defineProperties(obj, {
        baz: { value: 4, enumerable: false, configurable: true, writable: true }
    });

    // baz is non-enumerable
    var txt = JSON.stringify(obj, [ 'foo', 'bar', 'baz' ]);
    print(txt);

    // different order
    var txt = JSON.stringify(obj, [ 'baz', 'foo', 'bar' ]);
    print(txt);
}

function stringifyPropertyListTest2() {
    // test that inherited properties are also correctly enumerated
    // when using a PropertyList

    var proto = {};

    function F() {
        // quux and baz are inherited
        this.foo = 1;
        this.bar = 2;
    }
    F.prototype = proto;

    var obj;

    Object.defineProperties(proto, {
        quux: { value: 3, enumerable: true, writable: true, configurable: true },
        baz: { value: 4, enumerable: false, writable: true, configurable: true },
    });

    obj = new F();

    var txt = JSON.stringify(obj, [ 'foo', 'bar', 'baz' ]);
    print(txt);

    var txt = JSON.stringify(obj, [ 'baz', 'foo', 'bar' ]);
    print(txt);
}

try {
    stringifyPropertyListTest1();
    stringifyPropertyListTest2();
} catch (e) {
    print(e.name);
}

/*===
{"foo":1,"baz":4}
{"foo":1,"baz":4,"bar":2}
{"foo":1,"baz":4}
{"foo":1,"1.2":"val:1.2","2.2":"val:2.2","NaN":"val:NaN"}
===*/

/* Test invalid values in the property list: anything other than a number,
 * a string, a Number object, or a String object is ignored.
 */

function stringifyPropertyListTest3() {
    var obj = { foo: 1, bar: 2, quux: 3, baz: 4 };

    // add some properties to ensure invalid PropertyList keys are not
    // coerced incorrectly and look up one of these
    obj['' + true] = 'val:true';
    obj['' + false] = 'val:false';
    obj['null'] = 'val:null';
    obj['0'] = 'val:0';
    obj['1'] = 'val:1';

    // these will be legitimately accessed, numbers are coerced
    obj['1.2'] = 'val:1.2';
    obj['2.2'] = 'val:2.2';
    obj['NaN'] = 'val:NaN';

    // undefined will be skipped
    print(JSON.stringify(obj, [ 'foo', undefined, 'baz' ]));

    // null, true, false will be skipped
    print(JSON.stringify(obj, [ 'foo', null, 'baz', true, false, 'bar' ]));

    // function will be skipped, Date will be skipped, array and object
    // will be skipped
    print(JSON.stringify(obj, [ 'foo', function () {}, new Date(0), {}, [], 'baz' ]));

    // null will be skipped
    // ToString(1.2) = '1.2'; ToString(new Number(2.2)) = '2.2', ToString(0/0) = 'NaN'
    print(JSON.stringify(obj, [ 'foo', null, 1.2, new Number(2.2), 0/0 ]));
}

try {
    stringifyPropertyListTest3();
} catch (e) {
    print(e.name);
}


