@awayfl/avm2
Version:
Virtual machine for executing AS3 code
106 lines (92 loc) • 3.4 kB
text/typescript
import { ASObject } from './ASObject';
import { axCoerceString } from '../run/axCoerceString';
import { AXFunction } from '../run/AXFunction';
import { axIsCallable } from '../run/axIsCallable';
import { Errors } from '../errors';
import { transformJSValueToAS } from './transformJSValueToAS';
import { transformASValueToJS } from './transformASValueToJS';
import { walk } from './walk';
export class ASJSON extends ASObject {
static parse(text: string, reviver: AXFunction = null): any {
text = axCoerceString(text);
if (reviver !== null && !axIsCallable(reviver)) {
this.sec.throwError('TypeError', Errors.CheckTypeFailedError, reviver, 'Function');
}
if (text === null) {
this.sec.throwError('SyntaxError', Errors.JSONInvalidParseInput);
}
let unfiltered: Object;
try {
unfiltered = transformJSValueToAS(this.sec, JSON.parse(text), true);
} catch (e) {
this.sec.throwError('SyntaxError', Errors.JSONInvalidParseInput);
}
if (reviver === null) {
return unfiltered;
}
return walk(this.sec, { '': unfiltered }, '', reviver.value);
}
static stringify(value: any, replacer = null, space = null): string {
// We deliberately deviate from ECMA-262 and throw on
// invalid replacer parameter.
const sec = typeof replacer === 'object' ? replacer?.sec : null;
if (replacer !== null) {
if (!sec || !(sec.AXFunction.axIsType(replacer) || sec.AXArray.axIsType(replacer))) {
this.sec.throwError('TypeError', Errors.JSONInvalidReplacer);
}
}
let gap;
if (typeof space === 'string') {
gap = space.length > 10 ? space.substring(0, 10) : space;
} else if (typeof space === 'number') {
gap = ' '.substring(0, Math.min(10, space | 0));
} else {
// We follow ECMA-262 and silently ignore invalid space parameter.
gap = '';
}
if (replacer === null) {
return this.stringifySpecializedToString(value, null, null, gap);
} else if (sec.AXArray.axIsType(replacer)) {
return this.stringifySpecializedToString(value, this.computePropertyList(replacer.value),
null, gap);
} else { // replacer is Function
return this.stringifySpecializedToString(value, null, replacer.value, gap);
}
}
// ECMA-262 5th ed, section 15.12.3 stringify, step 4.b
private static computePropertyList(r: any[]): string[] {
const propertyList = [];
const alreadyAdded = Object.create(null);
for (let i = 0, length = r.length; i < length; i++) {
if (!r.hasOwnProperty(<any>i)) {
continue;
}
const v = r[i];
let item: string = null;
if (typeof v === 'string') {
item = v;
} else if (typeof v === 'number') {
item = axCoerceString(v);
}
if (item !== null && !alreadyAdded[item]) {
alreadyAdded[item] = true;
propertyList.push(item);
}
}
return propertyList;
}
private static stringifySpecializedToString(
value: Object, replacerArray: any [], replacerFunction: (key: string, value: any) => any, gap: string): string {
// In AS3 |JSON.stringify(undefined)| returns "null", while JS returns |undefined|.
// TODO: Is there anything to be done in case of a |replacerFunction| function?
if (value === undefined) {
return 'null';
}
const jsValue = transformASValueToJS(this.sec, value, true, replacerFunction);
try {
return JSON.stringify(jsValue, null, gap);
} catch (e) {
this.sec.throwError('TypeError', Errors.JSONCyclicStructure);
}
}
}