13  Backward Compatibility (Upgrades.qml)

When you rename, remove, or restructure options between module versions, older .jasp files saved by users will contain outdated option names. The Upgrades.qml mechanism automatically transforms old options to the new format when a user opens an older file.

13.1 How It Works

flowchart LR
    A[User opens old .jasp file] --> B{Saved version < current?}
    B -- Yes --> C[Apply Upgrades.qml transforms]
    C --> D[Analysis runs with updated options]
    B -- No --> D

Place an Upgrades.qml file in inst/ (alongside Description.qml). JASP chains upgrades automatically — if a file was saved at version 0.1 and the module is now at 0.4, JASP applies 0.1→0.2, then 0.2→0.3, then 0.3→0.4 in sequence.

13.2 File Structure

import QtQuick
import JASP.Module

Upgrades
{
    Upgrade
    {
        functionName:  "TTestIndependentSamples"
        fromVersion:   "0.16"
        toVersion:     "0.17"

        ChangeRename { from: "confidenceInterval"; to: "ciWidth" }
        ChangeSetValue { name: "effectSizeType"; jsonValue: "\"cohen\"" }
    }

    Upgrade
    {
        functionName:  "TTestIndependentSamples"
        fromVersion:   "0.17"
        toVersion:     "0.18"

        ChangeRemove { name: "legacyOption" }
    }
}

13.3 Change Types

13.3.1 ChangeRename

Rename an option, preserving its value:

ChangeRename { from: "oldName"; to: "newName" }

13.3.2 ChangeCopy

Copy an option’s value to a new name (original remains):

ChangeCopy { from: "source"; to: "target" }

13.3.3 ChangeSetValue

Set an option to a fixed value:

// String
ChangeSetValue { name: "method"; jsonValue: "\"bootstrap\"" }

// Number
ChangeSetValue { name: "ciWidth"; jsonValue: "95" }

// Boolean
ChangeSetValue { name: "effectSize"; jsonValue: "true" }

// Array
ChangeSetValue { name: "variables"; jsonValue: "[\"x\", \"y\"]" }

// Object
ChangeSetValue { name: "config"; jsonValue: "{\"a\": 1, \"b\": 2}" }
Note

The jsonValue must be valid JSON wrapped in a QML string. String values need escaped inner quotes: "\"value\"".

13.3.4 ChangeJS

Run a JavaScript function to compute the new value. The function receives the full options object (with all prior changes applied) and must return the new value:

ChangeJS
{
    name:     "newOption"
    jsFunction: function(options)
    {
        // Convert a radio button value to separate booleans
        if (options["oldRadio"] === "optionA")
            return true;
        return false;
    }
}

ChangeJS is the most powerful transform — use it when the conversion logic is non-trivial.

13.3.4.1 Utility Functions

Inside ChangeJS, you can use qmlUtils helpers:

  • qmlUtils.encodeAllColumnNames(value) — encode column names for internal storage.
  • qmlUtils.decodeAllColumnNames(value) — decode column names for display.
  • qmlUtils.encodeJson(value) / qmlUtils.decodeJson(value) — JSON serialization.

13.3.5 ChangeRemove

Remove an option entirely:

ChangeRemove { name: "obsoleteOption" }

13.4 Conditional Changes

Add a condition to any change. The change only applies when the condition evaluates to true:

ChangeRename
{
    from: "oldName"
    to:   "newName"
    condition: function(options) { return options["someFlag"] === true; }
}

If no condition is specified, it defaults to true (always applied).

13.4.1 Reusing Conditions

Give a condition an id to reuse across multiple changes:

Upgrade
{
    functionName: "MyAnalysis"
    fromVersion:  "0.2"
    toVersion:    "0.3"

    ChangeJS
    {
        id:   "wasLegacyMode"
        name: "mode"
        condition: function(options) { return options["legacyMode"] === true; }
        jsFunction: function(options) { return "modern"; }
    }

    ChangeRemove
    {
        name: "legacyMode"
        condition: "wasLegacyMode"
    }
}

13.5 User Messages

Optionally show a warning when an upgrade is applied:

Upgrade
{
    functionName: "MyAnalysis"
    fromVersion:  "0.1"
    toVersion:    "0.2"
    msg:          qsTr("Options have been updated. Please review your settings.")

    ChangeRename { from: "old"; to: "new" }
}

Messages can also go on individual changes:

ChangeRemove { name: "dropped"; msg: qsTr("The 'dropped' option is no longer supported.") }

13.6 Version Tying

  • The version in Upgrades.qml matches your module version (from DESCRIPTION and Description.qml), not the JASP version.
  • You do not need an Upgrade block for every module version — only for versions where options actually changed.
  • JASP chains through intermediate versions automatically.

13.6.1 Pre-0.15 Modules

Modules that existed before JASP 0.15 (when versioning was managed by JASP itself) should use version 0.15 as the starting point and 0.14.3 as the fromVersion for the initial upgrade block.

13.7 When to Add Upgrades

TipRule of Thumb

If you change any option name in QML, add an Upgrades.qml entry. If you only add new options (with defaults), no upgrade is needed.

  • Renamed optionChangeRename
  • Removed optionChangeRemove
  • Changed semantics (e.g., radio button → checkboxes) → ChangeJS
  • Added new option with default → No upgrade needed (JASP fills in the default)