Schema Types
Every Schema has a Type field that controls how its documents are created, stored, and navigated within the data model. Choosing the right type is fundamental to a well-structured data model.
Normal
Normal is the default type. Documents of a Normal schema can be:
Created as standalone root-level entries, visible in the left navigation menu.
Embedded inside other documents as Document or DocumentCollection property values.
Referenced from other documents via Reference or ReferenceCollection properties.
Use Normal for any entity that has an independent identity and may need to be listed, searched, and referenced across the project — items, characters, quests, abilities, etc.
{ "Id": "Sword", "Name": "Iron Sword", "Damage": 15 }
Component
Component schemas model sub-objects that are always embedded inside another document and never exist as standalone root-level entries.
Do not appear in the left navigation menu.
Can only be used as a value for a
DocumentorDocumentCollectionproperty.Useful for reusable sub-structures with no independent identity.
Typical examples: a Stat block inside a Character, a DropEntry inside a LootTable,
a BonusEffect inside a Weapon.
{
"Id": "Hero_Arbalest",
"BaseStats": { "HP": 100, "Speed": 4, "Damage": 12 }
}
Settings
Settings is a singleton type. Exactly one document of this schema may exist in the root-level collection. It cannot be embedded inside other documents.
Created automatically on first access and cannot be deleted.
Appears in the navigation menu as a direct link (no list page).
Suited for project-wide configuration:
GameSettings,BalanceConfig,GlobalRules.
{ "Id": "settings", "DifficultyMultiplier": 1.5, "MaxPlayersPerRoom": 4 }
Union (Tagged Union)
A Union schema — also known as a Discriminated Union, Sum Type, or $oneOf — lets a
single collection slot or property hold documents of different shapes in a type-safe way.
A Union schema does not define properties itself. Instead it declares a set of variant schemas (any Normal or Component schema).
Why Union instead of the other inheritance patterns
See Implementing Inheritance for a full comparison, but in short:
Pattern |
Storage cost |
UI |
Schema refactoring |
|---|---|---|---|
Composition |
Extra |
Standard form |
Move fields to Item |
Merging |
All fields on every doc |
All fields always visible |
Edit one big schema |
Aggregation |
Nullable ref per variant |
Multiple nullable refs |
Add a new ref field |
Union |
Only active variant’s fields |
Type picker, sparse form |
Add a new variant |
Key properties of Union:
Sparse storage — unset variant fields are not written to disk; only the active variant’s properties are stored.
First-class UI — when editing a Union field the editor shows a type-selector dropdown and only renders the form for the chosen variant.
Defining a Union schema
Create the variant schemas (Normal or Component) with their own properties.
Create a new schema, set Type to Union, and list the variant schemas in Variants.
Use the Union schema as the
ReferenceTypeof aReference,ReferenceCollection,Document, orDocumentCollectionproperty on another schema.
Storage format
Unlike many serialisation libraries that use a "$type" or "@class" string field
to tag the variant, Charon uses property-key presence as the discriminator.
Each union slot is serialised as a single-key wrapper object whose key is the
variant schema name and whose value is the document body:
{ "Weapon": { "Id": "Sword01", "Damage": 15, "WeaponType": "Melee" } }
{ "Armor": { "Id": "Plate01", "Defense": 8 } }
{ "Shield": { "Id": "Shield01","BlockChance": 0.25 }, /* Weapon: null, "Armor": null, are ommited */ }
The variant is identified by whichever key is present in the wrapper — "Weapon",
"Armor", "Shield", etc. There is no "$type" string anywhere in the stored data; the schema name is the discriminator.
A full collection of union documents therefore looks like:
{
"Collections": {
"ShopItem": [
{ "Weapon": { "Id": "Sword01", "Damage": 15, "WeaponType": "Melee" } },
{ "Armor": { "Id": "Plate01", "Defense": 8 } },
{ "Shield": { "Id": "Shield01","BlockChance": 0.25 } }
]
}
}
Generated source code
Union schemas generate a container type in the target language. Each variant is a nullable field inside that container; exactly one field is non-null at runtime.
Malformed Union values
A Union value is well-formed when the wrapper object contains exactly two keys:
Id and exactly one variant key. Two malformed states can occur — usually as a result
of a bulk import, a hand-crafted JSON payload, or a patch applied to a Union field without
accounting for the wrapper format.
Empty union — the Id is present but no variant key is set:
{ "Id": "item_01" }
Conflicting union — two or more variant keys are set simultaneously:
{ "Id": "item_01", "Weapon": { "Damage": 15 }, "Potion": { "HP": 5 } }
Both states are detected by the checkConstraints validation option (enabled by default)
and reported as separate error codes in the validation report:
Empty union →
emptyUnionerrorConflicting union →
conflictingUnionOptionserror, listing the conflicting variant names
Repairing malformed unions
The resolveConflictingUnions repair option fixes both cases automatically. It can be
used with DATA VALIDATE or
DATA IMPORT:
# repair in-place
charon DATA VALIDATE --dataBase "gamedata.json" \
--validationOptions repair resolveConflictingUnions
# repair while importing
charon DATA IMPORT --dataBase "gamedata.json" --input "./patch.json" \
--validationOptions repair resolveConflictingUnions
Repair behaviour:
Malformed state |
What the repair does |
|---|---|
Empty union |
Erases the union value entirely (sets it to |
Conflicting union |
Keeps the last variant in schema-property order; all other variant values are discarded. |
Note
The “last variant in schema-property order” is determined by the order of variant schemas as listed in the Union schema definition, not by the order of keys in the stored JSON. If the outcome of the automatic repair is not acceptable, correct the source data manually before importing.
Note
The Union type is the recommended approach when you need heterogeneous collections
and want first-class editor support. Prefer it over Aggregation for new designs.