serde enum formatting

Guide for serde enum formatting

Serde Enum Variant Formatting

Overview

This document explains how to correctly format enum variants in JSON files for Rust Serde deserialization.

Problem

Serde's default JSON serialization for enums follows specific patterns that must be matched in JSON data files. Mismatches cause parsing errors like:
ERROR Failed to parse tutorials: invalid type: unit variant, expected struct variant

Enum Variant Types

1. Unit Variants

Rust Definition:
pub enum CompletionTrigger {
    OpenInventory,
    EquipItem,
    AttackEnemy,
    // ...
}
Correct JSON:
{
    "completion_trigger": "AttackEnemy"
}
Incorrect JSON:
{
    "completion_trigger": {
        "AttackEnemy": null
    }
}

2. Struct Variants

Rust Definition:
pub enum CompletionTrigger {
    UseItem { item_id: String },
    TalkToNPC { npc_id: String },
    ReachLocation { x: f32, z: f32, radius: f32 },
    GainXP { skill: String, amount: i32 },
    Custom { event_id: String },
}
Correct JSON:
{
    "completion_trigger": {
        "UseItem": {
            "item_id": "rohu"
        }
    }
}
Incorrect JSON:
{
    "completion_trigger": "UseItem"
}

3. Tuple Variants

Rust Definition:
pub enum Message {
    Move(f32, f32),
    Write(String),
}
Correct JSON:
{
    "message": {
        "Move": [10.5, 20.3]
    }
}

Common Errors and Fixes

Error 1: Unit Variant as Struct

Error Message:
invalid type: unit variant, expected struct variant at line 13 column 46
Cause:
{
    "completion_trigger": "Custom"
}
Fix:
{
    "completion_trigger": {
        "Custom": {
            "event_id": "click_anywhere"
        }
    }
}

Error 2: Struct Variant as Unit

Error Message:
invalid type: struct variant, expected unit variant
Cause:
{
    "completion_trigger": {
        "AttackEnemy": null
    }
}
Fix:
{
    "completion_trigger": "AttackEnemy"
}

Tutorial System Example

Before (Incorrect)

{
    "steps": [
        {
            "step_id": "welcome",
            "completion_trigger": "Custom",  // ❌ Should be struct variant
            "hint": "Click anywhere"
        },
        {
            "step_id": "find_enemy",
            "completion_trigger": {
                "AttackEnemy": null  // ❌ Should be unit variant
            },
            "hint": "Look for enemies"
        }
    ]
}

After (Correct)

{
    "steps": [
        {
            "step_id": "welcome",
            "completion_trigger": {
                "Custom": {
                    "event_id": "click_anywhere"
                }
            },
            "hint": "Click anywhere"
        },
        {
            "step_id": "find_enemy",
            "completion_trigger": "AttackEnemy",
            "hint": "Look for enemies"
        }
    ]
}

Verification Strategy

1. Check Rust Definition

Look at the enum definition to determine variant type:
pub enum CompletionTrigger {
    OpenInventory,              // Unit variant
    EquipItem,                  // Unit variant
    AttackEnemy,                // Unit variant
    UseItem { item_id: String }, // Struct variant
    Custom { event_id: String }, // Struct variant
}

2. Match JSON Format

  • Unit variants: Use string directly
  • Struct variants: Use object with variant name as key

3. Test Parsing

Run the game and check logs:
cargo run --bin legends_client 2>&1 | grep "Failed to parse"
If parsing succeeds, you'll see:
INFO Loaded 3 tutorials

Serde Attributes

Changing Default Behavior

You can customize Serde's enum serialization with attributes:
#[derive(Deserialize, Serialize)]
#[serde(tag = "type")]  // Internally tagged
pub enum CompletionTrigger {
    OpenInventory,
    UseItem { item_id: String },
}
JSON with internal tagging:
{
    "type": "UseItem",
    "item_id": "rohu"
}
Note: The current codebase uses Serde's default (externally tagged) format.

Best Practices

  1. Document enum types: Add comments to Rust enums indicating variant types
  2. Validate JSON: Use a JSON schema validator if possible
  3. Test incrementally: Test each enum variant individually
  4. Use consistent formatting: Follow the same pattern across all data files