Quick answer: Use .As<T>() on each Variant value. Manually map dictionary keys to Resource fields; Godot won’t auto-convert between Dictionary and a typed Resource.

A save system loads game state into a Godot.Collections.Dictionary, then tries to populate a PlayerData Resource. Direct assignment throws InvalidCastException — Variant doesn’t auto-unwrap into custom Resource types.

Manual Field Mapping

public static PlayerData FromDict(Godot.Collections.Dictionary dict)
{
    return new PlayerData {
        Hp = dict["hp"].As<int>(),
        Name = dict["name"].As<string>(),
        Position = dict["position"].AsVector3(),
        Inventory = dict["inventory"].As<Godot.Collections.Array<string>>(),
    };
}

Explicit per-key conversion. Type-safe; compile-time errors if you misspell a property.

Safe Key Access

if (dict.TryGetValue("hp", out Variant hpVar))
{
    Hp = hpVar.As<int>();
}
else
{
    Hp = 100;   // default
}

For data from disk where keys may be missing (old saves), guard each access. Provides defaults for new fields.

Nested Resources

WeaponData = WeaponData.FromDict(dict["weapon"].As<Godot.Collections.Dictionary>())

Recurse: each nested type implements its own FromDict. Composes cleanly.

Round-Trip via JSON

For pure-data Resources, JSON skips Variant entirely:

var json = Json.Stringify(dict);
var data = System.Text.Json.JsonSerializer.Deserialize<PlayerData>(json);

Newtonsoft / System.Text.Json handle nested types automatically. Use when Resource is mostly POCO.

Verifying

Load saved dict; resource fields populated. Edit a field; save; reload; values match. No InvalidCastException.

“Variant is typed at runtime, not compile time. Convert explicitly and defensively.”

For large data models, generate FromDict / ToDict code via a source generator — reduces boilerplate and stays type-safe.