diff --git a/src/engine/runtime.js b/src/engine/runtime.js index ce6a0801eef..d0101f437fb 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -464,6 +464,13 @@ class Runtime extends EventEmitter { warpTimer: false }; + this.serializationOptions = { + /** + * Allows variables to be saved with types other than strings, numbers, and booleans. + */ + ignoreVariableSerialization: false + }; + this.debug = false; this._lastStepTime = Date.now(); diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js index 33c822205d2..4ce8c52e369 100644 --- a/src/serialization/sb3.js +++ b/src/serialization/sb3.js @@ -484,7 +484,8 @@ const serializeSound = function (sound) { // Using some bugs, it can be possible to get values like undefined, null, or complex objects into // variables or lists. This will cause make the project unusable after exporting without JSON editing // as it will fail validation in scratch-parser. -// To avoid this, we'll convert those objects to strings before saving them. +// To avoid this, we'll convert those objects to strings before saving them by default. +// If the project goes against this (for certain extensions), dont bother doing this step. const isVariableValueSafeForJSON = value => ( typeof value === 'number' || typeof value === 'string' || @@ -516,11 +517,12 @@ const makeSafeForJSON = value => { /** * Serialize the given variables object. * @param {object} variables The variables to be serialized. + * @param {boolean} optIgnoreVarTypes If true, will ignore variable type serialization * @return {object} A serialized representation of the variables. They get * separated by type to compress the representation of each given variable and * reduce duplicate information. */ -const serializeVariables = function (variables) { +const serializeVariables = function (variables, optIgnoreVarTypes) { const obj = Object.create(null); // separate out variables into types at the top level so we don't have // keep track of a type for each @@ -534,12 +536,18 @@ const serializeVariables = function (variables) { continue; } if (v.type === Variable.LIST_TYPE) { - obj.lists[varId] = [v.name, makeSafeForJSON(v.value)]; + obj.lists[varId] = [ + v.name, + optIgnoreVarTypes ? v.value : makeSafeForJSON(v.value) + ]; continue; } // otherwise should be a scalar type - obj.variables[varId] = [v.name, makeSafeForJSON(v.value)]; + obj.variables[varId] = [ + v.name, + optIgnoreVarTypes ? v.value : makeSafeForJSON(v.value) + ]; // only scalar vars have the potential to be cloud vars if (v.isCloud) obj.variables[varId].push(true); } @@ -580,14 +588,15 @@ const serializeComments = function (comments) { * for saving and loading this target. * @param {object} target The target to be serialized. * @param {Set} extensions A set of extensions to add extension IDs to + * @param {boolean} optIgnoreVarTypes If true, will ignore variable type serialization * @return {object} A serialized representation of the given target. */ -const serializeTarget = function (target, extensions) { +const serializeTarget = function (target, extensions, optIgnoreVarTypes) { const obj = Object.create(null); let targetExtensions = []; obj.isStage = target.isStage; obj.name = obj.isStage ? 'Stage' : target.name; - const vars = serializeVariables(target.variables); + const vars = serializeVariables(target.variables, optIgnoreVarTypes); obj.variables = vars.variables; obj.lists = vars.lists; obj.broadcasts = vars.broadcasts; @@ -730,7 +739,12 @@ const serialize = function (runtime, targetId, {allowOptimization = true} = {}) }); } - const serializedTargets = flattenedOriginalTargets.map(t => serializeTarget(t, extensions)) + const serializationOptions = runtime.serializationOptions; + const serializedTargets = flattenedOriginalTargets.map(t => serializeTarget( + t, + extensions, + serializationOptions.ignoreVariableSerialization + )) .map((serialized, index) => { // can't serialize extensionStorage until the list of used extensions is fully known const target = originalTargetsToSerialize[index];