Serialize Infinity and NaN to 0

This commit is contained in:
Paul Kaplan 2018-12-04 10:52:49 -05:00
parent 122443a75f
commit 7e96ef2985
5 changed files with 64 additions and 4 deletions

View file

@ -14,6 +14,7 @@ const StageLayering = require('../engine/stage-layering');
const log = require('../util/log'); const log = require('../util/log');
const uid = require('../util/uid'); const uid = require('../util/uid');
const MathUtil = require('../util/math-util'); const MathUtil = require('../util/math-util');
const StringUtil = require('../util/string-util');
const {loadCostume} = require('../import/load-costume.js'); const {loadCostume} = require('../import/load-costume.js');
const {loadSound} = require('../import/load-sound.js'); const {loadSound} = require('../import/load-sound.js');
@ -522,7 +523,7 @@ const serialize = function (runtime, targetId) {
const layerOrdering = getSimplifiedLayerOrdering(originalTargetsToSerialize); const layerOrdering = getSimplifiedLayerOrdering(originalTargetsToSerialize);
const flattenedOriginalTargets = JSON.parse(JSON.stringify(originalTargetsToSerialize)); const flattenedOriginalTargets = JSON.parse(StringUtil.stringify(originalTargetsToSerialize));
// If the renderer is attached, and we're serializing a whole project (not a sprite) // If the renderer is attached, and we're serializing a whole project (not a sprite)
// add a temporary layerOrder property to each target. // add a temporary layerOrder property to each target.

View file

@ -36,6 +36,27 @@ class StringUtil {
return [text, null]; return [text, null];
} }
/**
* A customized version of JSON.stringify that sets Infinity/NaN to 0,
* instead of the default (null).
* Needed because null is not of type number, but Infinity/NaN are, which
* can lead to serialization producing JSON that isn't valid based on the parser schema.
* It is also consistent with the behavior of saving 2.0 projects.
* This is only needed when stringifying an object for saving.
*
* @param {!object} obj - The object to serialize
* @return {!string} The JSON.stringified string with Infinity/NaN replaced with 0
*/
static stringify (obj) {
return JSON.stringify(obj, (_key, value) => {
if (typeof value === 'number' &&
(value === Infinity || value === -Infinity || isNaN(value))){
return 0;
}
return value;
});
}
} }
module.exports = StringUtil; module.exports = StringUtil;

View file

@ -381,7 +381,7 @@ class VirtualMachine extends EventEmitter {
exportSprite (targetId, optZipType) { exportSprite (targetId, optZipType) {
const soundDescs = serializeSounds(this.runtime, targetId); const soundDescs = serializeSounds(this.runtime, targetId);
const costumeDescs = serializeCostumes(this.runtime, targetId); const costumeDescs = serializeCostumes(this.runtime, targetId);
const spriteJson = JSON.stringify(sb3.serialize(this.runtime, targetId)); const spriteJson = StringUtil.stringify(sb3.serialize(this.runtime, targetId));
const zip = new JSZip(); const zip = new JSZip();
zip.file('sprite.json', spriteJson); zip.file('sprite.json', spriteJson);
@ -401,7 +401,7 @@ class VirtualMachine extends EventEmitter {
* @return {string} Serialized state of the runtime. * @return {string} Serialized state of the runtime.
*/ */
toJSON () { toJSON () {
return JSON.stringify(sb3.serialize(this.runtime)); return StringUtil.stringify(sb3.serialize(this.runtime));
} }
// TODO do we still need this function? Keeping it here so as not to introduce // TODO do we still need this function? Keeping it here so as not to introduce

View file

@ -63,3 +63,14 @@ test('unusedName', t => {
); );
t.end(); t.end();
}); });
test('stringify', t => {
const obj = {a: Infinity, b: NaN, c: -Infinity, d: 23, e: 'str'};
const parsed = JSON.parse(StringUtil.stringify(obj));
t.equal(parsed.a, 0);
t.equal(parsed.b, 0);
t.equal(parsed.c, 0);
t.equal(parsed.d, 23);
t.equal(parsed.e, 'str');
t.end();
});

View file

@ -982,3 +982,30 @@ test('Starting the VM emits an event', t => {
t.equal(started, true); t.equal(started, true);
t.end(); t.end();
}); });
test('toJSON encodes Infinity/NaN as 0, not null', t => {
const vm = new VirtualMachine();
const runtime = vm.runtime;
const spr1 = new Sprite(null, runtime);
const stage = spr1.createClone();
stage.isStage = true;
stage.volume = Infinity;
stage.tempo = NaN;
stage.createVariable('id1', 'name1', '');
stage.variables.id1.value = Infinity;
stage.createVariable('id2', 'name2', '');
stage.variables.id1.value = -Infinity;
stage.createVariable('id3', 'name3', '');
stage.variables.id1.value = NaN;
runtime.targets = [stage];
const json = JSON.parse(vm.toJSON());
t.equal(json.targets[0].volume, 0);
t.equal(json.targets[0].tempo, 0);
t.equal(json.targets[0].variables.id1[1], 0);
t.equal(json.targets[0].variables.id2[1], 0);
t.equal(json.targets[0].variables.id3[1], 0);
t.end();
});