ID Serialization
← Discriminator Mapping | Next: Reference Serialization →
See also:
- Naming Conventions for key naming conventions
- Annotation Reference (ID Configuration) for complete configuration keys
1. ID Feature Resolution
The serializer determines which features to use as ID:
- If
idFeaturesis specified (via annotation or config) → use those features in the specified order - Otherwise → use all features with
eID="true"in the EClass, in declaration order - If no ID features are found → no
_idfield is written
When idFeatures points to an EReference (containment), the serializer follows the reference and uses the contained object's ID definition.
1.1 No ID Attribute Case
When an EClass has no eID="true" attribute and no idFeatures are configured, the serializer intentionally does not write an _id field. This design decision is based on:
- Serialization: There's no meaningful ID value to write
- Deserialization: If we wrote a synthetic value (e.g., URI fragment), there would be no attribute to deserialize it back into
- Consistency: Matches the behavior of codec v1, which logs a warning and skips ID serialization in this case
Example: GeoJSON Point has no ID attribute, so the output is simply:
{
"type": "Point",
"coordinates": [8.6821, 50.1109]
}Feature Ordering: When multiple features form a combined ID, the order is preserved as specified:
- For
idFeatures="lastName, firstName"→ combined ID is"Doe-John"(not"John-Doe") - For
idFeatures="firstName, lastName"→ combined ID is"John-Doe"
This ordering applies to both PLAIN format (concatenation order) and STRUCTURED format (property order in JSON object).
2. ID Key Mode
Controls how ID is represented in the JSON output:
| Mode | Description | Example (single) | Example (multiple) |
|---|---|---|---|
ID_ONLY (default) | Only _id key | "_id": "john" | "_id": "John-Doe" |
BOTH | _id + original feature names | "_id": "john", "myId": "john" | "_id": "John-Doe", "firstName": "John", "lastName": "Doe" |
FEATURE_ONLY | Only original feature names | "myId": "john" | "firstName": "John", "lastName": "Doe" |
NONE | No ID serialization | (no _id field) | (no _id field) |
Note: When
IdKeyMode=NONE, theIdStrategyis ignored — no ID serialization happens at all.
3. ID Format (Plain vs Structured)
Per Serialization Strategies, ID values can be plain or structured.
3.1 Plain Format (default) - Single Feature
EAnnotation:
<eClassifiers xsi:type="ecore:EClass" name="Person">
<eStructuralFeatures xsi:type="ecore:EAttribute" name="myId" eType=".../EString" iD="true"/>
</eClassifiers>Config Builder:
// No config needed - uses defaults (PLAIN format, ID_ONLY mode)
IdSerializationConfig config = IdSerializationConfig.builder().build();Resulting JSON:
{
"_id": "john"
}3.2 Plain Format - Multiple Features
EAnnotation:
<eClassifiers xsi:type="ecore:EClass" name="Person">
<eAnnotations source="http://eclipse.org/fennec/codec">
<details key="idStrategy" value="COMBINED"/>
<details key="idFeatures" value="firstName, lastName, sequence"/>
<details key="idSeparator" value="-"/>
</eAnnotations>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="firstName" eType=".../EString"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="lastName" eType=".../EString"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="sequence" eType=".../EString"/>
</eClassifiers>Config Builder:
CodecConfiguration config = CodecConfiguration.builder()
.idStrategy(IdStrategy.COMBINED)
.idFeatures("firstName", "lastName", "sequence")
.idSeparator("-")
.build();Resulting JSON:
{
"_id": "John-Doe-1"
}3.3 Structured Format - Single Feature
In STRUCTURED format, IdKeyMode controls what goes inside the _id object. The inner key for the combined/single ID value is configurable via idValueKey (default: id).
EAnnotation:
<eClassifiers xsi:type="ecore:EClass" name="Person">
<eAnnotations source="http://eclipse.org/fennec/codec">
<details key="idFormat" value="STRUCTURED"/>
</eAnnotations>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="myId" eType=".../EString" iD="true"/>
</eClassifiers>Config Builder:
CodecConfiguration config = CodecConfiguration.builder()
.idFormat(SerializationFormat.STRUCTURED)
.build();Resulting JSON (default: IdKeyMode=ID_ONLY, idValueKey="id"):
{
"_id": {
"id": "john"
}
}Note: The inner key
"id"comes fromidValueKey(default). WithIdKeyMode=FEATURE_ONLY, the feature name"myId"would be used instead. WithBOTH, both appear. See §7 for the full matrix.
3.4 Structured Format - Multiple Features
EAnnotation:
<eClassifiers xsi:type="ecore:EClass" name="Person">
<eAnnotations source="http://eclipse.org/fennec/codec">
<details key="idStrategy" value="COMBINED"/>
<details key="idFeatures" value="firstName, lastName, sequence"/>
<details key="idSeparator" value="-"/>
<details key="idFormat" value="STRUCTURED"/>
</eAnnotations>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="firstName" eType=".../EString"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="lastName" eType=".../EString"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="sequence" eType=".../EString"/>
</eClassifiers>Config Builder:
CodecConfiguration config = CodecConfiguration.builder()
.idStrategy(IdStrategy.COMBINED)
.idFeatures("firstName", "lastName", "sequence")
.idSeparator("-")
.idFormat(SerializationFormat.STRUCTURED)
.build();Resulting JSON (default: IdKeyMode=ID_ONLY, idValueKey="id"):
{
"_id": {
"id": "John-Doe-1",
"separator": "-"
}
}Note: With
IdKeyMode=ID_ONLY, only the combined value (viaidValueKey) and separator are written inside_id. WithFEATURE_ONLY, individual feature keys replace the combined value. WithBOTH, all appear. See §7 for the full matrix.
4. ID with EReference (Contained ID Object)
When idFeatures points to a containment EReference, the ID is resolved from the contained object.
Example: ExampleIDClass01 (contained IdClass01 has no eID, uses idFeatures)
The contained class IdClass01:
<eClassifiers xsi:type="ecore:EClass" name="IdClass01">
<eAnnotations source="http://eclipse.org/fennec/codec">
<details key="idStrategy" value="COMBINED"/>
<details key="idSeparator" value="_"/>
<details key="idFeatures" value="userGroup, userId"/>
</eAnnotations>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="userId" eType="ecore:EDataType .../ELong"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="userGroup" eType="ecore:EDataType .../EString"/>
</eClassifiers>The parent class pointing to it:
<eClassifiers xsi:type="ecore:EClass" name="ExampleIDClass01">
<eAnnotations source="http://eclipse.org/fennec/codec">
<details key="idFeatures" value="myId"/>
</eAnnotations>
<eStructuralFeatures xsi:type="ecore:EReference" name="myId" eType="#//IdClass01" containment="true"/>
</eClassifiers>Or via Java Builder:
CodecConfiguration config = CodecConfiguration.builder()
.forClass(ExamplePackage.Literals.EXAMPLE_ID_CLASS01)
.idFeatures("myId") // Points to EReference
.end()
.build();Resulting Output:
{
"_type": "...#//ExampleIDClass01",
"_id": "sales_42",
"test": "...",
"myId": {
"userId": 42,
"userGroup": "sales"
}
}5. ID Configuration
5.0 Configuration Keys
| Annotation Key | Property Key | Default | Description |
|---|---|---|---|
idStrategy | codec.idStrategy | ID_FIELD | ID building strategy (ID_FIELD, COMBINED) |
idFormat | codec.idFormat | PLAIN | Output format (PLAIN, STRUCTURED) |
idKey | codec.idKey | _id | Outer JSON key |
idValueKey | codec.idValueKey | id | Inner key in STRUCTURED format |
idFeatures | codec.idFeatures | All eID="true" | Comma-separated feature names for COMBINED |
idSeparator | codec.idSeparator | - | Separator when combining multiple features |
idSeparatorKey | codec.idSeparatorKey | separator | Key for separator field |
idSeparatorSerialize | codec.idSeparatorSerialize | true | Include separator in output |
idKeyMode | codec.idKeyMode | ID_ONLY | ID_ONLY, BOTH, or FEATURE_ONLY |
idOnTop | codec.idOnTop | false | ID before type in output |
idValueWriterName | codec.idValueWriterName | — | Custom value writer |
idValueReaderName | codec.idValueReaderName | — | Custom value reader |
| — | codec.idScope | ALL | Strategy scope (runtime-only) |
| — | codec.idFormatScope | ALL | Format scope (runtime-only) |
Note:
idScopeandidFormatScopeare runtime-only configuration - no EAnnotation equivalent. See Configuration Resolution (section 5).
IdStrategy values:
| Value | Description |
|---|---|
ID_FIELD (default) | Use EMF eID attribute(s) |
COMBINED | Combine multiple features with separator |
5.1 Separator Serialization
With multiple ID features (COMBINED strategy), the separator can optionally be included in the JSON output. This applies to both PLAIN and STRUCTURED formats.
PLAIN Format
With idSeparatorSerialize=true (default):
{
"_id": "John-Doe",
"_separator": "-"
}With idSeparatorSerialize=false:
{
"_id": "John-Doe"
}STRUCTURED Format
With idSeparatorSerialize=true (default):
{
"_id": {
"separator": "-",
"firstName": "John",
"lastName": "Doe"
}
}With idSeparatorSerialize=false:
{
"_id": {
"firstName": "John",
"lastName": "Doe"
}
}Key Naming Convention
- PLAIN format: Uses underscore prefix →
_separator(configurable viaidSeparatorKey) - STRUCTURED format: No prefix (inner key) →
separator(configurable viaidSeparatorKey)
Deserialization Behavior
When idSeparatorSerialize=true, the deserializer reads the separator from the JSON, so no configuration is needed. When idSeparatorSerialize=false, the separator must be configured for deserialization (more compact output, but requires matching config).
6. Configuration Examples
Example 1: Minimal Configuration (Defaults)
Config Builder:
CodecConfiguration config = CodecConfiguration.builder()
.idStrategy(IdStrategy.COMBINED)
.idFeatures("firstName", "lastName")
.idSeparator("-")
.build();Resulting JSON: (idKeyMode=ID_ONLY, idFormat=PLAIN, idKey="_id" are defaults)
{
"_id": "John-Doe",
"firstName": "John",
"lastName": "Doe"
}Example 2: BOTH Mode with STRUCTURED Format
Config Builder:
CodecConfiguration config = CodecConfiguration.builder()
.idStrategy(IdStrategy.COMBINED)
.idFeatures("firstName", "lastName")
.idSeparator("-")
.idKeyMode(IdKeyMode.BOTH)
.idFormat(SerializationFormat.STRUCTURED)
.idKey("id")
.build();Resulting JSON: (idKey="id" overrides default "_id", BOTH writes combined value via idValueKey AND feature names)
{
"id": {
"id": "John-Doe",
"firstName": "John",
"lastName": "Doe",
"separator": "-"
},
"firstName": "John",
"lastName": "Doe"
}Example 3: FEATURE_ONLY Mode
Config Builder:
CodecConfiguration config = CodecConfiguration.builder()
.idStrategy(IdStrategy.COMBINED)
.idFeatures("firstName", "lastName")
.idKeyMode(IdKeyMode.FEATURE_ONLY)
.build();Resulting JSON: (no _id property, features are used directly for identification)
{
"firstName": "John",
"lastName": "Doe"
}Example 4: NONE Mode (No ID Serialization)
Config Builder:
CodecConfiguration config = CodecConfiguration.builder()
.idKeyMode(IdKeyMode.NONE)
.build();Resulting JSON: (IdStrategy is ignored, no ID fields written at all)
{
"firstName": "John",
"lastName": "Doe"
}Note:
NONEdiffers fromFEATURE_ONLY— withFEATURE_ONLY, the ID features are still written as regular properties. WithNONE, no ID-related serialization happens at all (features are only written if they appear in the normal feature serialization).
7. Complete Examples Summary
Note: In STRUCTURED format, the inner key for the combined/single value comes from
idValueKey(default:"id"). Feature names are used whenIdKeyModeincludes features (BOTHorFEATURE_ONLY).
7.1 Single ID Field (ID_FIELD Strategy)
Model: myId marked eID="true", default idValueKey="id".
PLAIN Format:
| IdKeyMode | Output |
|---|---|
| ID_ONLY (default) | {"_id": "john", ...} |
| BOTH | {"_id": "john", "myId": "john", ...} |
| FEATURE_ONLY | {"myId": "john", ...} |
| NONE | {...} (no ID fields) |
STRUCTURED Format:
| IdKeyMode | Output |
|---|---|
| ID_ONLY (default) | {"_id": {"id": "john"}, ...} |
| BOTH | {"_id": {"id": "john", "myId": "john"}, ...} |
| FEATURE_ONLY | {"_id": {"myId": "john"}, ...} |
| NONE | {...} (no _id object) |
7.2 Multiple ID Fields (COMBINED Strategy)
Model: firstName, lastName, sequence with idSeparator="-", default idValueKey="id".
PLAIN Format:
| IdKeyMode | Output |
|---|---|
| ID_ONLY (default) | {"_id": "John-Doe-1", "_separator": "-", ...} |
| BOTH | {"_id": "John-Doe-1", "_separator": "-", "firstName": "John", "lastName": "Doe", "sequence": "1", ...} |
| FEATURE_ONLY | {"firstName": "John", "lastName": "Doe", "sequence": "1", ...} |
| NONE | {...} (no ID fields) |
STRUCTURED Format:
| IdKeyMode | Output |
|---|---|
| ID_ONLY (default) | {"_id": {"id": "John-Doe-1", "separator": "-"}, ...} |
| BOTH | {"_id": {"id": "John-Doe-1", "firstName": "John", "lastName": "Doe", "sequence": "1", "separator": "-"}, ...} |
| FEATURE_ONLY | {"_id": {"firstName": "John", "lastName": "Doe", "sequence": "1", "separator": "-"}, ...} |
| NONE | {...} (no _id object) |
Separator serialization: When
idSeparatorSerialize=false, the"_separator"(PLAIN) or"separator"(STRUCTURED) entries are omitted. See §5.1 for details.
8. ID Configuration Scope
ID configuration follows the same scope model as Type Configuration.
Type and ID Configuration Alignment
The ID and Type systems share a unified configuration architecture. This table summarizes what applies to each:
| Feature | Type | ID | Notes |
|---|---|---|---|
| StrategyScope enum | ✅ typeScope | ✅ idScope | Same enum, same semantics |
| Separate format scope | ✅ typeFormatScope | ✅ idFormatScope | Independent from strategy scope |
| Per-class config | ✅ | ✅ | Via EAnnotation or builder |
| Per-reference config | ✅ (limited) | ❌ | ID identifies object, not access path |
| Global-only scope | ✅ | ✅ | Scope settings are global only |
| Disable via enum | ✅ TypeStrategy.NONE | ✅ IdKeyMode.NONE | No separate boolean toggle needed |
| DeserializationMode | ✅ STRICT/LENIENT/AUTO | ❌ (always lenient) | ID parsing is simpler |
| Type hint mode | ✅ HINT/OVERRIDE | ❌ | ID has no "hint" concept |
| Direction-specific | ✅ codec.ser.* | ✅ codec.ser.* | Via property maps |
Why ID is simpler:
- Type resolution involves complex strategy matching (URI, NAME, MAPPED, etc.)
- ID just reads values and populates features
- No "unknown ID" fallback needed - either the feature exists or it doesn't
8.1 StrategyScope Values
The idScope and idFormatScope settings control where ID configuration applies:
enum StrategyScope {
ALL, // Root + all containments + all non-containments (DEFAULT)
ROOT_ONLY, // Root object only, children use defaults
ROOT_CONTAINMENT, // Root + containment references (ref.isContainment=true)
ROOT_NON_CONTAINMENT // Root + non-containment references (ref.isContainment=false)
}Scope interpretation:
ALL= ID settings apply everywhere (default)ROOT_ONLY= ID settings apply to root object onlyROOT_CONTAINMENT= ID settings apply to root OR when serializing via a containment referenceROOT_NON_CONTAINMENT= ID settings apply to root OR when serializing via a non-containment reference
8.2 Separate Scopes for Strategy and Format
Like Type, ID has independent scopes for strategy and format:
| Configuration | What it controls | Scope Property |
|---|---|---|
idStrategy | HOW to build ID (ID_FIELD, COMBINED) | idScope |
idFormat | WHERE to put ID info (PLAIN, STRUCTURED) | idFormatScope |
Example: ID_FIELD strategy at root only, STRUCTURED format everywhere:
CodecConfiguration.builder()
.idStrategy(IdStrategy.ID_FIELD)
.idScope(StrategyScope.ROOT_ONLY)
.idFormat(SerializationFormat.STRUCTURED)
.idFormatScope(StrategyScope.ALL)
.build();Note:
idScopeandidFormatScopeare runtime-only (🔧) - no EAnnotation equivalent.
8.3 Setting Scope Rules
| Setting | Configuration Levels | Scope Property |
|---|---|---|
idKeyMode | Global, Per-class | - (always applies) |
idStrategy | Global, Per-class | idScope |
idFormat | Global, Per-class | idFormatScope |
idKey | Global, Per-class | - (follows strategy scope) |
idFeatures | Per-class only | - |
separator | Global, Per-class | - (follows strategy scope) |
keyMode | Global, Per-class | - (follows strategy scope) |
Note: idScope and idFormatScope are global-level only settings - they cannot be overridden per-class.
8.4 Why No Per-Reference ID Configuration?
Per-reference ID configuration is intentionally not supported. Reasons:
- Semantic mismatch: ID identifies an object itself, not how it's referenced. A Person's ID is
"John-Doe"regardless of whether accessed viacompany.ceoordepartment.employees[0]. - Consistency: The same object appearing in multiple references should have the same ID representation.
- Deserialization simplicity: ID parsing doesn't need to know the reference context.
If you need custom ID handling for specific scenarios, consider:
- Custom value writers/readers at the class level (see Custom Value Readers/Writers)
- Different EClasses with different ID configurations for genuinely different identification needs
8.5 Containment Behavior
When serializing a containment hierarchy, each contained object uses its own ID configuration (subject to scope settings):
Example:
Company (idFeatures="companyId")
└── employees: Person[*] (containment)
Person (idFeatures="firstName,lastName", separator="-")Output:
{
"_id": "ACME-123",
"name": "Acme Inc",
"employees": [
{
"_id": "John-Doe",
"firstName": "John",
"lastName": "Doe"
}
]
}Each class uses its own idFeatures and separator - no inheritance from parent.
8.6 Configuration Dependencies
When IdKeyMode=NONE, all other ID settings are effectively disabled — no ID is serialized, so idOnTop, idStrategy, idFormat, etc. have no effect.
idKeyMode | idOnTop (configured) | idOnTop (effective) |
|---|---|---|
| ID_ONLY / BOTH / FEATURE_ONLY | true | true |
| ID_ONLY / BOTH / FEATURE_ONLY | false | false |
| NONE | true | false (no ID serialized) |
| NONE | false | false |
Migration from v1: The old
useId=falseboolean is replaced byIdKeyMode.NONE. This is more explicit and fits naturally into the IdKeyMode enum rather than requiring a separate toggle.
8.7 Metadata Field Ordering (idOnTop) — Runtime Orchestration Constraint
The idOnTop setting is a runtime constraint that controls the position of the _id field relative to other metadata fields (_type, _supertype) in the serialized JSON. Although configured on the ID side, it affects the orchestrator (CodecEObjectSerializer) which determines the order in which the Type and ID serialization entries are invoked.
Serialization Order
| Setting | Field Order | Example |
|---|---|---|
idOnTop=true | _id key + EIDAttribute feature → _type → _supertype → remaining features | {"_id":"john","myId":"john","_type":"Person","name":"John"} |
idOnTop=false (default) | _type → _supertype → _id key + features | {"_type":"Person","_id":"john","myId":"john","name":"John"} |
Note: When
idOnTop=true, both the synthetic_idkey and the EIDAttribute feature (theeID="true"EAttribute, resolved viaeClass.getEIDAttribute()) are promoted to the front of the output. This matches the behaviour of tabular exporters (CSV/ODS/XLSX), whereidOnTopfloats the EIDAttribute column to front.
Cross-reference: See also 06-type.md §5.0.2 for the type-side perspective, and 01-architecture.md §4 for the overall serialization flow.
Use Cases
idOnTop=true: Useful for databases like MongoDB where_idas the first field improves indexing performanceidOnTop=false(default): Type information appears first — better for human readability and API conventions
Configuration
Java Builder:
CodecConfiguration config = CodecConfiguration.builder()
.idOnTop(true) // Put _id before _type
.build();EAnnotation (on EClass):
<eClassifiers xsi:type="ecore:EClass" name="Person">
<eAnnotations source="http://eclipse.org/fennec/codec">
<details key="idOnTop" value="true"/>
</eAnnotations>
</eClassifiers>Deserialization
The idOnTop setting only affects serialization. During deserialization, the codec accepts fields in any order — JSON objects are inherently unordered. See Architecture §5.1 for the deferred properties mechanism.
9. Deserialization
9.1 ID Key Recognition
The deserializer recognizes the following property names as ID keys:
| Key | Description |
|---|---|
_id | Default ID key (recommended) |
id | Common alternative (if explicitly configured) |
Note: Unlike
_type, which has multiple auto-recognized variants,_idis the only default. Usingidrequires explicit configuration viaidKeysetting.
9.2 Format Detection
The deserializer auto-detects the format from the JSON structure:
| Input | Detected Format |
|---|---|
"_id": "string" | PLAIN |
"_id": 123 | PLAIN (numeric) |
"_id": { ... } | STRUCTURED |
9.3 PLAIN Format Deserialization
For single ID feature:
- Read the
_idvalue as string - Convert to the ID attribute's data type
- Set on the EObject
For multiple ID features (combined ID):
- Read the
_idvalue as string (e.g.,"John-Doe") - If
_separatorfield is present, use that separator - Otherwise, use configured separator (default:
-) - Split by separator:
["John", "Doe"] - Map to features in order:
firstName="John",lastName="Doe"
Example:
{
"_id": "John-Doe-42",
"_separator": "-"
}Deserializes to:
firstName = "John"lastName = "Doe"sequence = "42"(converted to appropriate type)
9.4 STRUCTURED Format Deserialization
- Read the
_idobject - Map each key to the corresponding feature
- Convert values to appropriate data types
Example:
{
"_id": {
"separator": "-",
"firstName": "John",
"lastName": "Doe",
"sequence": 42
}
}The separator field is informational (used if re-serializing in PLAIN format).
9.5 No ID Attribute Case
When the JSON contains _id but the EClass has no ID attribute:
| Scenario | Behavior |
|---|---|
_id in JSON, no ID attribute | Log INFO, ID value available for URI fragment |
_id in JSON, idFeatures configured | Use configured features |
No _id in JSON, has ID attribute | Feature uses default value |
The ID value is not lost - it can be used by the resource for URI fragment assignment.
9.6 EReference-Based ID Deserialization
When idFeatures points to an EReference:
{
"_id": "sales_42",
"myId": {
"userGroup": "sales",
"userId": 42
}
}The deserializer:
- Detects
_idis a combined ID from contained object - The contained object (
myId) is deserialized normally - The
_idvalue can be used for URI fragment (optional)
9.7 ID Deserialization Error Handling
Unlike Type deserialization (which has complex resolution strategies), ID deserialization is straightforward. Errors are handled as follows:
| Scenario | Behavior | Severity |
|---|---|---|
_id can't be split (wrong part count) | Use value as-is for first feature | WARNING |
_id feature type mismatch | Attempt type conversion | ERROR if conversion fails |
STRUCTURED _id missing expected key | Use default value for feature | WARNING |
_id present but no ID features configured | ID available for URI fragment | INFO |
Example - Separator mismatch:
// Config: separator="-", idFeatures=["firstName", "lastName", "seq"]
{ "_id": "John_Doe_42" } // Uses "_" instead of "-"Result: Cannot split by "-", WARNING logged, value used as-is for firstName.
Lenient Behavior: ID deserialization is inherently lenient:
- Auto-detects PLAIN vs STRUCTURED format
- Attempts type conversion for mismatched types
- Provides meaningful fallbacks instead of failing
Note: Unlike Type which has
DeserializationMode(STRICT/LENIENT/AUTO_DETECT), ID deserialization always uses lenient behavior because ID parsing is simpler and less format-dependent.
10. ID Serialization Flow
┌─────────────────────────────────────────────────────────────────────────────┐
│ ID SERIALIZATION FLOW │
└─────────────────────────────────────────────────────────────────────────────┘
INPUT: EObject to serialize, effective IdSerializationConfig
┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. CHECK IF ID SERIALIZATION IS ENABLED │
│ ─────────────────────────────────────────────────────────────────────────│
│ Is idKeyMode=NONE? │
│ ├─ YES → Skip ID serialization entirely ─────────────────→ DONE ✓ │
│ │ (IdStrategy is ignored, no _id field, no ID features written) │
│ │ (ValueWriter is also ignored — NONE is a hard disable) │
│ └─ NO → Continue │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 2. CHECK CUSTOM VALUE WRITER │
│ ─────────────────────────────────────────────────────────────────────────│
│ Is idValueWriterName configured? │
│ ├─ YES → Full delegation to custom IdValueWriter service │
│ │ - Has: JsonGenerator, EffectiveCodecConfig, DiagnosticCollector │
│ │ - Writer handles everything: value, format, keys, keyMode │
│ │ - Framework does NOT apply format/strategy/keyMode │
│ │ → DONE ✓ │
│ │ │
│ └─ NO → Continue with built-in logic (step 3) │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 3. RESOLVE ID FEATURES │
│ ─────────────────────────────────────────────────────────────────────────│
│ Is idFeatures configured (annotation or runtime)? │
│ ├─ YES → Use specified features in given order │
│ └─ NO → Use all features with eID="true" in declaration order │
│ │
│ Any ID features found? │
│ ├─ NO → No _id field is written ────────────────────────→ DONE ✓ │
│ └─ YES → Continue │
│ │
│ Is any ID feature an EReference (containment)? │
│ ├─ YES → Follow reference, use contained object's ID definition │
│ └─ NO → Use feature values directly │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 4. DETERMINE ID VALUE │
│ ─────────────────────────────────────────────────────────────────────────│
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ IdStrategy │ Value Generation │ │
│ ├────────────────┼────────────────────────────────────────────────┤ │
│ │ ID_FIELD │ Read eID attribute value(s) from EObject │ │
│ │ COMBINED │ Read idFeatures values, combine with separator │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Single feature: value = feature value (e.g., "john") │
│ Multiple features: combinedValue = values joined by idSeparator │
│ (e.g., "John-Doe-1") │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 5. APPLY FORMAT + KEY MODE │
│ ─────────────────────────────────────────────────────────────────────────│
│ This step determines HOW and WHAT gets written for the ID. │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ PLAIN FORMAT │ │
│ ├────────────┬─────────────────────────────────────────────────────────┤ │
│ │ ID_ONLY │ Write combined value at idKey │ │
│ │ │ "_id": "John-Doe" │ │
│ │ BOTH │ Write combined value at idKey + individual features │ │
│ │ │ "_id": "John-Doe", "firstName": "John", ... │ │
│ │ FEAT_ONLY │ Write individual features only (no idKey) │ │
│ │ │ "firstName": "John", "lastName": "Doe" │ │
│ └────────────┴─────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ STRUCTURED FORMAT (inside _id object) │ │
│ ├────────────┬─────────────────────────────────────────────────────────┤ │
│ │ ID_ONLY │ Write combined value via idValueKey (default: "id") │ │
│ │ │ { "id": "John-Doe" } │ │
│ │ BOTH │ Write idValueKey + individual feature entries │ │
│ │ │ { "id": "John-Doe", "firstName": "John", ... } │ │
│ │ FEAT_ONLY │ Write individual feature entries only │ │
│ │ │ { "firstName": "John", "lastName": "Doe" } │ │
│ └────────────┴─────────────────────────────────────────────────────────┘ │
│ │
│ If COMBINED strategy + idSeparatorSerialize=true: │
│ PLAIN: Write separator at _separator (or configured separatorKey) │
│ STRUCTURED: Write separator inside _id object (no prefix) │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 6. APPLY FIELD ORDERING — Runtime Orchestration Constraint (idOnTop) │
│ ─────────────────────────────────────────────────────────────────────────│
│ The idOnTop property is a RUNTIME CONSTRAINT that affects both the │
│ Type and ID serialization entries. The CodecEObjectSerializer evaluates │
│ idOnTop from the effective ID config and invokes the serialization │
│ entries in the appropriate order: │
│ │
│ ┌────────────┬─────────────────────────────────────────────────────────┐ │
│ │ idOnTop │ Field Write Order │ │
│ ├────────────┼─────────────────────────────────────────────────────────┤ │
│ │ true │ _id + EIDAttribute feature → Type → SuperType → rest │ │
│ │ false │ Type → SuperType → _id + features (natural order) │ │
│ └────────────┴─────────────────────────────────────────────────────────┘ │
│ │
│ This step is NOT internal to the ID flow — it is the orchestrator's │
│ responsibility. The ID flow (steps 1-5) produces the ID output; │
│ the orchestrator determines WHEN it is written relative to type. │
│ │
│ See also: 06-type.md §5.0.2 for the type-side perspective. │
│ ──────────────────────────────────────────────────────→ DONE ✓ │
└─────────────────────────────────────────────────────────────────────────────┘11. ID Deserialization Flow
┌─────────────────────────────────────────────────────────────────────────────┐
│ ID DESERIALIZATION FLOW │
└─────────────────────────────────────────────────────────────────────────────┘
INPUT: JSON object, effective IdSerializationConfig, target EClass
┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. CHECK IF ID DESERIALIZATION IS ENABLED │
│ ─────────────────────────────────────────────────────────────────────────│
│ Is idKeyMode=NONE? │
│ ├─ YES → Skip ID deserialization entirely ───────────────→ DONE ✓ │
│ │ (Config symmetry: if ser wrote no ID, deser should not expect │
│ │ it. ValueReader is also ignored — NONE is a hard disable.) │
│ └─ NO → Continue │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 2. CHECK CUSTOM VALUE READER │
│ ─────────────────────────────────────────────────────────────────────────│
│ Is idValueReaderName configured? │
│ ├─ YES → Full delegation to custom IdValueReader service │
│ │ - Has: JsonParser, EffectiveCodecConfig, DiagnosticCollector │
│ │ - Reader handles everything: locate field, parse, set values │
│ │ - Framework does NOT apply format detection/built-in parsing │
│ │ → DONE ✓ │
│ │ │
│ └─ NO → Continue with built-in logic (step 3) │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 3. LOCATE ID FIELD │
│ ─────────────────────────────────────────────────────────────────────────│
│ Look for idKey (default: "_id") in JSON object. │
│ │
│ Found? │
│ ├─ NO → No ID to deserialize │
│ │ Has ID attribute in EClass? → Feature uses default value │
│ │ ──────────────────────────────────────────────→ DONE ✓ │
│ └─ YES → Continue │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 4. DETECT FORMAT │
│ ─────────────────────────────────────────────────────────────────────────│
│ Inspect the JSON value type at idKey: │
│ │
│ ┌─────────────┬──────────────────────────────────────────────────────┐ │
│ │ JSON Value │ Detected Format │ │
│ ├──────────────┼─────────────────────────────────────────────────────┤ │
│ │ String │ PLAIN │ │
│ │ Number │ PLAIN (numeric) │ │
│ │ Object { } │ STRUCTURED │ │
│ └──────────────┴─────────────────────────────────────────────────────┘ │
│ │
│ Note: Format is auto-detected from JSON structure, no config needed. │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 5. RESOLVE ID FEATURES IN ECLASS │
│ ─────────────────────────────────────────────────────────────────────────│
│ Is idFeatures configured? │
│ ├─ YES → Use specified features │
│ └─ NO → Use eID="true" attributes │
│ │
│ Any ID features found? │
│ ├─ NO → ID value available for URI fragment (INFO) ─────→ DONE ✓ │
│ └─ YES → Continue │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 6. PARSE ID VALUE │
│ ─────────────────────────────────────────────────────────────────────────│
│ │
│ ┌──── PLAIN FORMAT ──────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Single feature? │ │
│ │ ├─ YES → Read value, convert to feature type, set on EObject │ │
│ │ └─ NO (multiple features): │ │
│ │ 1. Read combined value as string (e.g., "John-Doe-1") │ │
│ │ 2. Get separator: │ │
│ │ - If _separator field present in JSON → use that │ │
│ │ - Otherwise → use configured idSeparator (default: "-") │ │
│ │ 3. Split by separator → ["John", "Doe", "1"] │ │
│ │ 4. Map to features in order: │ │
│ │ firstName="John", lastName="Doe", sequence="1" │ │
│ │ 5. Convert each to feature's data type │ │
│ │ │ │
│ │ Error: Can't split (wrong part count)? │ │
│ │ → WARNING, use value as-is for first feature │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──── STRUCTURED FORMAT ─────────────────────────────────────────────┐ │
│ │ │ │
│ │ Read the _id JSON object. │ │
│ │ For each expected key (idValueKey, feature names, separatorKey): │ │
│ │ - Present → read value, convert to feature type │ │
│ │ - Missing → use default value for feature (WARNING) │ │
│ │ │ │
│ │ Separator field is informational (stored for potential │ │
│ │ re-serialization in PLAIN format). │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Type conversion fails? → ERROR │
│ ──────────────────────────────────────────────────────→ DONE ✓ │
└─────────────────────────────────────────────────────────────────────────────┘12. Default ID Settings
| Setting | Property Key | Default Value |
|---|---|---|
| Strategy | codec.idStrategy | ID_FIELD |
| Format | codec.idFormat | PLAIN |
| ID Key | codec.idKey | _id |
| Value Key | codec.idValueKey | id |
| Separator | codec.idSeparator | - |
| Separator Key | codec.idSeparatorKey | separator |
| Key Mode | codec.idKeyMode | ID_ONLY |
| Serialize Separator | codec.idSeparatorSerialize | true |
| ID on Top | codec.idOnTop | false |
| Scope | codec.idScope | ALL |
| Format Scope | codec.idFormatScope | ALL |
13. Configuration Validation Rules
This section documents validation rules for ID Configuration that are checked when building the effective configuration. See also 16-annotation-reference.md for the ID "Invalid Configurations" table.
13.1 Property Applicability Rules
| Rule ID | Property | Invalid At | Severity | Description |
|---|---|---|---|---|
| ID-V1 | idStrategy | EReference | ERROR | ID strategy is class-intrinsic (identity doesn't change based on access path) |
| ID-V2 | idFeatures | EReference | ERROR | ID features are class-specific |
| ID-V3 | idSeparator | EReference | ERROR | Separator is class-specific |
| ID-V4 | idSeparatorKey | EReference | ERROR | Separator config is class-specific |
| ID-V5 | idSeparatorSerialize | EReference | ERROR | Separator config is class-specific |
| ID-V6 | idKeyMode | EReference | ERROR | Key mode is class-intrinsic |
| ID-V7 | idValueKey | EReference | ERROR | Inner key is class-specific |
| ID-V8 | idOnTop | EReference | ERROR | Field ordering is class-specific |
| ID-V9 | idValueReaderName | EReference | ERROR | Value reader is class-intrinsic |
| ID-V10 | idValueWriterName | EReference | ERROR | Value writer is class-intrinsic |
| ID-V11 | idScope | EAnnotation | WARNING | Runtime-only property, ignored if found in EAnnotation |
| ID-V12 | idFormatScope | EAnnotation | WARNING | Runtime-only property, ignored if found in EAnnotation |
| ID-V13 | Any id* key | EAttribute | ERROR | ID configuration not applicable to attributes |
Note:
idFormatandidKeyARE valid on EReference (presentation can vary by context). See 16-annotation-reference.md for the full valid-level matrix.
13.2 Related Validation Rules
- Type Configuration: See 06-type.md section 7 for Type-specific validation rules
- SuperType Configuration: See 07-supertype.md section 6.0 for SuperType constraints
- Error Handling: See 15-error-handling.md section 6.10 for annotation parsing error patterns
