From 50e25da1cd32ee81b8eb7d92c500046e781d26a4 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sun, 1 Mar 2026 21:14:07 -0800 Subject: [PATCH 1/4] Enhance serialization with opt_ignoreVarTypes parameter Updated serialization logic to include an option for ignoring variable type serialization. --- src/serialization/sb3.js | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js index 33c822205d2..3a99d7c82d0 100644 --- a/src/serialization/sb3.js +++ b/src/serialization/sb3.js @@ -484,13 +484,14 @@ 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' || typeof value === 'boolean' ); -const makeSafeForJSON = value => { +const makeSafeForJSON = (value) => { if (Array.isArray(value)) { let copy = null; for (let i = 0; i < value.length; i++) { @@ -580,14 +581,17 @@ 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} opt_ignoreVarTypes 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, opt_ignoreVarTypes) { 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 = opt_ignoreVarTypes ? + target.variables : + serializeVariables(target.variables); obj.variables = vars.variables; obj.lists = vars.lists; obj.broadcasts = vars.broadcasts; @@ -730,16 +734,21 @@ const serialize = function (runtime, targetId, {allowOptimization = true} = {}) }); } - const serializedTargets = flattenedOriginalTargets.map(t => serializeTarget(t, extensions)) - .map((serialized, index) => { - // can't serialize extensionStorage until the list of used extensions is fully known - const target = originalTargetsToSerialize[index]; - const targetExtensionStorage = serializeExtensionStorage(target.extensionStorage, extensions); - if (targetExtensionStorage) { - serialized.extensionStorage = targetExtensionStorage; - } - return serialized; - }); + 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]; + const targetExtensionStorage = serializeExtensionStorage(target.extensionStorage, extensions); + if (targetExtensionStorage) { + serialized.extensionStorage = targetExtensionStorage; + } + return serialized; + }); const fonts = runtime.fontManager.serializeJSON(); From c61ca4cbd40722eaff983249e732cae748ba97d1 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sun, 1 Mar 2026 21:20:14 -0800 Subject: [PATCH 2/4] Introduce serialization options for variable types Add serialization options to allow variable type saving --- src/engine/runtime.js | 7 +++++++ 1 file changed, 7 insertions(+) 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(); From 59218c8c74ca06a1144c94fb795d916eaa02a03d Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sun, 1 Mar 2026 21:22:10 -0800 Subject: [PATCH 3/4] lint --- src/serialization/sb3.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js index 3a99d7c82d0..dc7b9cfa252 100644 --- a/src/serialization/sb3.js +++ b/src/serialization/sb3.js @@ -491,7 +491,7 @@ const isVariableValueSafeForJSON = value => ( typeof value === 'string' || typeof value === 'boolean' ); -const makeSafeForJSON = (value) => { +const makeSafeForJSON = value => { if (Array.isArray(value)) { let copy = null; for (let i = 0; i < value.length; i++) { @@ -581,15 +581,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} opt_ignoreVarTypes If true, will ignore variable type serialization + * @param {boolean} optIgnoreVarTypes If true, will ignore variable type serialization * @return {object} A serialized representation of the given target. */ -const serializeTarget = function (target, extensions, opt_ignoreVarTypes) { +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 = opt_ignoreVarTypes ? + const vars = optIgnoreVarTypes ? target.variables : serializeVariables(target.variables); obj.variables = vars.variables; @@ -740,15 +740,15 @@ const serialize = function (runtime, targetId, {allowOptimization = true} = {}) extensions, serializationOptions.ignoreVariableSerialization )) - .map((serialized, index) => { - // can't serialize extensionStorage until the list of used extensions is fully known - const target = originalTargetsToSerialize[index]; - const targetExtensionStorage = serializeExtensionStorage(target.extensionStorage, extensions); - if (targetExtensionStorage) { - serialized.extensionStorage = targetExtensionStorage; - } - return serialized; - }); + .map((serialized, index) => { + // can't serialize extensionStorage until the list of used extensions is fully known + const target = originalTargetsToSerialize[index]; + const targetExtensionStorage = serializeExtensionStorage(target.extensionStorage, extensions); + if (targetExtensionStorage) { + serialized.extensionStorage = targetExtensionStorage; + } + return serialized; + }); const fonts = runtime.fontManager.serializeJSON(); From 2af3c673e9eec4e6ce8274453870c4fb5668c1e4 Mon Sep 17 00:00:00 2001 From: SharkPool <139097378+SharkPool-SP@users.noreply.github.com> Date: Sun, 1 Mar 2026 21:34:56 -0800 Subject: [PATCH 4/4] Add optIgnoreVarTypes parameter to serializeVariables --- src/serialization/sb3.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js index dc7b9cfa252..4ce8c52e369 100644 --- a/src/serialization/sb3.js +++ b/src/serialization/sb3.js @@ -517,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 @@ -535,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); } @@ -589,9 +596,7 @@ const serializeTarget = function (target, extensions, optIgnoreVarTypes) { let targetExtensions = []; obj.isStage = target.isStage; obj.name = obj.isStage ? 'Stage' : target.name; - const vars = optIgnoreVarTypes ? - target.variables : - serializeVariables(target.variables); + const vars = serializeVariables(target.variables, optIgnoreVarTypes); obj.variables = vars.variables; obj.lists = vars.lists; obj.broadcasts = vars.broadcasts;