Document Merge Logic
When DATA IMPORT or DATA APPLYPATCH merges incoming (modified) data with the
existing (original) data, it follows a set of deterministic rules per property type.
Understanding these rules is essential for predicting the outcome of partial updates,
patch application, and SafeUpdate operations.
Core Principle
Every merge operation takes two document trees — original (left) and modified (right) — and produces a merged result. The fundamental rule is:
If modified provides a value for a property → the merged result uses the modified value.
If modified does not provide a value for a property → the merged result keeps the original value.
Both absent → the property is omitted from the result.
The rule applies recursively: properties that are themselves documents are merged
property-by-property using the same rules, and collections are merged item-by-item
using the document’s Id as the key.
Note
“Modified does not provide a value” means the property key is absent from the incoming
document. A property present in modified with an explicit null value is treated as
an intentional deletion, not a missing value.
Import Mode
The merge behaviour is governed by the import mode passed to the operation. Two modes affect merge in particular:
createAndUpdate(default)Modified values always win. Items absent from the modified collection are treated as deleted. This is the mode used by
DATA APPLYPATCH.safeUpdateA restricted mode that prevents deletions. Items absent from the modified collection are left unchanged. The
Idfield of a document is never overwritten regardless of what modified supplies.
Other modes (create, update, replace, delete) control which documents
are eligible for merge, but once a document is selected, the merge rules below apply.
Property-Level Merge Rules
Scalar Properties
Applies to: Text, Time, Date, Logical, Reference, Formula,
Number, Integer, PickList, MultiPickList.
If modified supplies a value, it overwrites the original unconditionally. If modified does not supply a value, the original is kept.
Special case — ``Id`` in SafeUpdate mode:
The Id property is never overwritten when the import mode is safeUpdate.
Even if modified provides an Id value, the original Id is preserved.
This prevents accidental re-identification of documents when patching subsets of fields.
LocalizedText Properties
LocalizedText values are merged per language rather than replaced wholesale:
For each language configured in Project Settings:
If modified contains a value for that language → use the modified value.
If modified does not contain that language key → keep the original value for that language.
If modified cannot be interpreted as a
LocalizedTextat all → the result isnull.If original cannot be interpreted as a
LocalizedText(e.g. property was empty before) → the modified value is used as-is with no per-language merging.
Translation notes are merged separately:
The comment field: modified wins if it has a comment; otherwise the original comment is kept.
Stale translation flags: the union (OR) of both sets — a language that was stale in either original or modified remains stale. Stale flags are then automatically cleared for any language whose value was explicitly supplied by modified.
Inline Document Properties
A property of type Document (an embedded sub-document) is merged recursively using
the same rules applied to top-level documents.
The four cases:
Original |
Modified |
Result |
|---|---|---|
absent / null |
document |
Merge(empty, modified) — see caveat |
document |
absent / null |
|
document |
document |
Recursive merge, property by property |
absent / null |
absent / null |
Property omitted from result |
Document Collections
DocumentCollection and ReferenceCollection properties are merged by document
Id. The set of Ids to iterate depends on the mode:
createAndUpdate — iterates the modified Ids. Documents present in original but absent from modified are treated as deleted and dropped from the result.
safeUpdate — iterates the original Ids. Documents present in original but absent from modified are left unchanged. Items in modified not in original are ignored.
Per-item resolution:
Original item |
Modified item |
Result |
|---|---|---|
exists |
absent (id not listed) |
Kept unchanged |
absent |
exists |
Created (added to result) |
exists |
|
Deleted (dropped), kept in |
exists |
exists |
|
For ReferenceCollection items (which store a reference object, not full data), when
both sides exist the modified reference object replaces the original without recursion —
there are no meaningful sub-properties to merge.
Null optional collections
If modified explicitly supplies null for an optional (non-required) collection property,
the result is null regardless of what original contained. In safeUpdate mode, an
empty modified collection means “no changes”, not “clear the collection”.
The Partial-Document Resurrection Caveat
A subtle but important edge case arises when:
A document was deleted from the original (original is
nullor absent).A patch was created that adds or modifies that document (modified contains a partial document — only the properties that changed).
DATA APPLYPATCH(createAndUpdatemode) applies the patch.
In this case the merge calls Merge(empty, partialModified, schema).
Since empty provides no values, every property takes its value from partialModified
where present — and is simply omitted where modified does not supply it.
The resulting document exists in the data, but only contains the properties included in the patch. Required properties that were absent in the original document — and were not listed in the patch — will be missing from the result.
Example:
Original Item/IronSword was deleted. A patch contains:
{
"Collections": {
"Item": {
"IronSword": { "Id": "IronSword", "Damage": 20 }
}
}
}
If the Item schema has a required Name field, the merged document will be:
{ "Id": "IronSword", "Damage": 20 }
Name is absent. Subsequent validation (checkRequirements) will report this as an
error.
How to avoid it:
When generating a patch for a new document, include all required fields in the patch, not only the diff.
When applying a patch that resurrects deleted documents, pass stricter
--validationOptionsto catch missing required fields immediately:charon DATA APPLYPATCH \ --dataBase target.json \ --input changes.patch.json \ --validationOptions repair checkRequirements checkReferences
If the resurrection is intentional but the document is expected to be incomplete, use
--validationOptions Noneand fix the document via a follow-up import.
Merge in Context
The merge logic is invoked by:
``DATA IMPORT`` — for every document that matches both the import input and the target data (matched by
Id). The mode is set by--mode.``DATA APPLYPATCH`` — always runs in
createAndUpdatemode. The patch format usesnullentries to express deletions, which interact directly with the merge rules described above.``DATA UPDATE`` (single document) — runs in
createAndUpdatemode on a single document.