Skip to content

feat: Add canonical serialization mode to event and variable serialization#8552

Open
ViktorVovk wants to merge 1 commit into
4ian:masterfrom
PlaytikaOSS:canonical-event-serialization-for-clean-git-diffs
Open

feat: Add canonical serialization mode to event and variable serialization#8552
ViktorVovk wants to merge 1 commit into
4ian:masterfrom
PlaytikaOSS:canonical-event-serialization-for-clean-git-diffs

Conversation

@ViktorVovk
Copy link
Copy Markdown
Contributor

Add canonicalEventSerialization preference for stable, diff-friendly event JSON

Problem

GDevelop's event serialization omits properties that hold default values — disabled, folded, inverted, await are only written to JSON when true; events, variables, subInstructions, loopIndexVariable, etc. are only written when non-empty. This causes two kinds of noise in git diffs:

  1. Line insertions / deletions — toggling a boolean flag adds or removes a line, shifting every subsequent line in the block.
  2. Unstable key order — JSON object keys are emitted in code-execution order, which differs between event types and changes when events are reorganised.

Both issues make per-event diffs hard to review in version-controlled projects.

Solution

Add a project-level preference canonicalEventSerialization (opt-in, default false). When enabled:

  • All conditionally-omitted properties are always written with their default values (false, "", []), so toggling a flag produces a single-line value change, not an insertion/deletion.
  • All JSON object keys are written in alphabetical order, giving a stable, predictable layout regardless of event type or serialization order.

The preference is controlled via gdevelop-settings.yaml (already read by the IDE on project open):

# gdevelop-settings.yaml
preferences:
  canonicalEventSerialization: true

- Introduced a global canonical mode in the Serializer class to ensure consistent JSON output.
- Updated various event serialization methods to include default values and alphabetical key ordering when canonical mode is enabled.
- Added support for canonical serialization in the Variable class.
- Enhanced the SerializerElement to handle canonical mode during JSON conversion.
- Updated related tests and preferences to accommodate the new serialization behavior.
@4ian
Copy link
Copy Markdown
Owner

4ian commented Apr 30, 2026

toggling a boolean flag adds or removes a line, shifting every subsequent line in the block.

Shouldn't this make things harder to review for a human (or even an AI) because there is less noise?

Unstable key order

Agree it's not great and probably something we can fix!

@ViktorVovk ViktorVovk marked this pull request as ready for review May 11, 2026 07:10
@ViktorVovk ViktorVovk requested a review from 4ian as a code owner May 11, 2026 07:10
@ViktorVovk
Copy link
Copy Markdown
Contributor Author

ViktorVovk commented May 11, 2026

Good day @4ian , and thank you for the preliminary review.

I’ll try to answer your question. The idea is that when the canonicalEventSerialization flag is enabled, the saved project JSON already contains all fields with their current values by default.

This way, when a value changes, only the value itself changes, making the diff much clearer — you can simply see that a property changed from true to false or vice versa.

With the default behavior, when a value is false, the field is omitted from the JSON entirely. When it becomes true, the field is added, and if it becomes false again, the field is removed once more. From our perspective, this actually makes the diff harder to read and understand.

@4ian
Copy link
Copy Markdown
Owner

4ian commented May 13, 2026

Ok, I see the reason behind this. We will see if we can make the implementation a bit less verbose with some dedicated helper (SetBoolAttributeOrEmpty? something that conveys that if "canonical" is not activated, a falsy or default value is not set). Then this will be good to go!

for (const auto& entry : sortedEntries) {
Value name(entry.first.c_str(), allocator);
Value childValue;
if (entry.second.isAttribute) {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpicking: remove the boolean entirely and instead relying on checking if entry.second.attributeValue != nullptr
(and same for entry.second.childElement != nullptr, out of safety)

private:
Serializer(){};

static bool s_canonicalMode;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually don't use prefix with hungarian notation:

Suggested change
static bool s_canonicalMode;
static bool canonicalMode;

Copy link
Copy Markdown
Owner

@4ian 4ian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a few comments and I think it will be fine to have this.
I would want ideally all SerializeTo methods to handle silently canonical, but I don't have yet a good idea of how to make this elegantly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants