Skip to content

ID Serialization

← Discriminator Mapping | Next: Reference Serialization →


See also:


1. ID Feature Resolution

The serializer determines which features to use as ID:

  1. If idFeatures is specified (via annotation or config) → use those features in the specified order
  2. Otherwise → use all features with eID="true" in the EClass, in declaration order
  3. If no ID features are foundno _id field 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:

json
{
  "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:

ModeDescriptionExample (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_ONLYOnly original feature names"myId": "john""firstName": "John", "lastName": "Doe"
NONENo ID serialization(no _id field)(no _id field)

Note: When IdKeyMode=NONE, the IdStrategy is 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:

xml
<eClassifiers xsi:type="ecore:EClass" name="Person">
  <eStructuralFeatures xsi:type="ecore:EAttribute" name="myId" eType=".../EString" iD="true"/>
</eClassifiers>

Config Builder:

java
// No config needed - uses defaults (PLAIN format, ID_ONLY mode)
IdSerializationConfig config = IdSerializationConfig.builder().build();

Resulting JSON:

json
{
  "_id": "john"
}

3.2 Plain Format - Multiple Features

EAnnotation:

xml
<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:

java
CodecConfiguration config = CodecConfiguration.builder()
    .idStrategy(IdStrategy.COMBINED)
    .idFeatures("firstName", "lastName", "sequence")
    .idSeparator("-")
    .build();

Resulting JSON:

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:

xml
<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:

java
CodecConfiguration config = CodecConfiguration.builder()
    .idFormat(SerializationFormat.STRUCTURED)
    .build();

Resulting JSON (default: IdKeyMode=ID_ONLY, idValueKey="id"):

json
{
  "_id": {
    "id": "john"
  }
}

Note: The inner key "id" comes from idValueKey (default). With IdKeyMode=FEATURE_ONLY, the feature name "myId" would be used instead. With BOTH, both appear. See §7 for the full matrix.

3.4 Structured Format - Multiple Features

EAnnotation:

xml
<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:

java
CodecConfiguration config = CodecConfiguration.builder()
    .idStrategy(IdStrategy.COMBINED)
    .idFeatures("firstName", "lastName", "sequence")
    .idSeparator("-")
    .idFormat(SerializationFormat.STRUCTURED)
    .build();

Resulting JSON (default: IdKeyMode=ID_ONLY, idValueKey="id"):

json
{
  "_id": {
    "id": "John-Doe-1",
    "separator": "-"
  }
}

Note: With IdKeyMode=ID_ONLY, only the combined value (via idValueKey) and separator are written inside _id. With FEATURE_ONLY, individual feature keys replace the combined value. With BOTH, 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:

xml
<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:

xml
<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:

java
CodecConfiguration config = CodecConfiguration.builder()
    .forClass(ExamplePackage.Literals.EXAMPLE_ID_CLASS01)
        .idFeatures("myId")  // Points to EReference
        .end()
    .build();

Resulting Output:

json
{
  "_type": "...#//ExampleIDClass01",
  "_id": "sales_42",
  "test": "...",
  "myId": {
    "userId": 42,
    "userGroup": "sales"
  }
}

5. ID Configuration

5.0 Configuration Keys

Annotation KeyProperty KeyDefaultDescription
idStrategycodec.idStrategyID_FIELDID building strategy (ID_FIELD, COMBINED)
idFormatcodec.idFormatPLAINOutput format (PLAIN, STRUCTURED)
idKeycodec.idKey_idOuter JSON key
idValueKeycodec.idValueKeyidInner key in STRUCTURED format
idFeaturescodec.idFeaturesAll eID="true"Comma-separated feature names for COMBINED
idSeparatorcodec.idSeparator-Separator when combining multiple features
idSeparatorKeycodec.idSeparatorKeyseparatorKey for separator field
idSeparatorSerializecodec.idSeparatorSerializetrueInclude separator in output
idKeyModecodec.idKeyModeID_ONLYID_ONLY, BOTH, or FEATURE_ONLY
idOnTopcodec.idOnTopfalseID before type in output
idValueWriterNamecodec.idValueWriterNameCustom value writer
idValueReaderNamecodec.idValueReaderNameCustom value reader
codec.idScopeALLStrategy scope (runtime-only)
codec.idFormatScopeALLFormat scope (runtime-only)

Note: idScope and idFormatScope are runtime-only configuration - no EAnnotation equivalent. See Configuration Resolution (section 5).

IdStrategy values:

ValueDescription
ID_FIELD (default)Use EMF eID attribute(s)
COMBINEDCombine 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):

json
{
  "_id": "John-Doe",
  "_separator": "-"
}

With idSeparatorSerialize=false:

json
{
  "_id": "John-Doe"
}

STRUCTURED Format

With idSeparatorSerialize=true (default):

json
{
  "_id": {
    "separator": "-",
    "firstName": "John",
    "lastName": "Doe"
  }
}

With idSeparatorSerialize=false:

json
{
  "_id": {
    "firstName": "John",
    "lastName": "Doe"
  }
}

Key Naming Convention

Per JSON Key Configuration:

  • PLAIN format: Uses underscore prefix → _separator (configurable via idSeparatorKey)
  • STRUCTURED format: No prefix (inner key) → separator (configurable via idSeparatorKey)

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:

java
CodecConfiguration config = CodecConfiguration.builder()
    .idStrategy(IdStrategy.COMBINED)
    .idFeatures("firstName", "lastName")
    .idSeparator("-")
    .build();

Resulting JSON: (idKeyMode=ID_ONLY, idFormat=PLAIN, idKey="_id" are defaults)

json
{
  "_id": "John-Doe",
  "firstName": "John",
  "lastName": "Doe"
}

Example 2: BOTH Mode with STRUCTURED Format

Config Builder:

java
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)

json
{
  "id": {
    "id": "John-Doe",
    "firstName": "John",
    "lastName": "Doe",
    "separator": "-"
  },
  "firstName": "John",
  "lastName": "Doe"
}

Example 3: FEATURE_ONLY Mode

Config Builder:

java
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)

json
{
  "firstName": "John",
  "lastName": "Doe"
}

Example 4: NONE Mode (No ID Serialization)

Config Builder:

java
CodecConfiguration config = CodecConfiguration.builder()
    .idKeyMode(IdKeyMode.NONE)
    .build();

Resulting JSON: (IdStrategy is ignored, no ID fields written at all)

json
{
  "firstName": "John",
  "lastName": "Doe"
}

Note: NONE differs from FEATURE_ONLY — with FEATURE_ONLY, the ID features are still written as regular properties. With NONE, 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 when IdKeyMode includes features (BOTH or FEATURE_ONLY).

7.1 Single ID Field (ID_FIELD Strategy)

Model: myId marked eID="true", default idValueKey="id".

PLAIN Format:

IdKeyModeOutput
ID_ONLY (default){"_id": "john", ...}
BOTH{"_id": "john", "myId": "john", ...}
FEATURE_ONLY{"myId": "john", ...}
NONE{...} (no ID fields)

STRUCTURED Format:

IdKeyModeOutput
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:

IdKeyModeOutput
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:

IdKeyModeOutput
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:

FeatureTypeIDNotes
StrategyScope enumtypeScopeidScopeSame enum, same semantics
Separate format scopetypeFormatScopeidFormatScopeIndependent from strategy scope
Per-class configVia EAnnotation or builder
Per-reference config✅ (limited)ID identifies object, not access path
Global-only scopeScope settings are global only
Disable via enumTypeStrategy.NONEIdKeyMode.NONENo separate boolean toggle needed
DeserializationMode✅ STRICT/LENIENT/AUTO❌ (always lenient)ID parsing is simpler
Type hint mode✅ HINT/OVERRIDEID has no "hint" concept
Direction-specificcodec.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:

java
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 only
  • ROOT_CONTAINMENT = ID settings apply to root OR when serializing via a containment reference
  • ROOT_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:

ConfigurationWhat it controlsScope Property
idStrategyHOW to build ID (ID_FIELD, COMBINED)idScope
idFormatWHERE to put ID info (PLAIN, STRUCTURED)idFormatScope

Example: ID_FIELD strategy at root only, STRUCTURED format everywhere:

java
CodecConfiguration.builder()
    .idStrategy(IdStrategy.ID_FIELD)
    .idScope(StrategyScope.ROOT_ONLY)
    .idFormat(SerializationFormat.STRUCTURED)
    .idFormatScope(StrategyScope.ALL)
    .build();

Note: idScope and idFormatScope are runtime-only (🔧) - no EAnnotation equivalent.

8.3 Setting Scope Rules

SettingConfiguration LevelsScope Property
idKeyModeGlobal, Per-class- (always applies)
idStrategyGlobal, Per-classidScope
idFormatGlobal, Per-classidFormatScope
idKeyGlobal, Per-class- (follows strategy scope)
idFeaturesPer-class only-
separatorGlobal, Per-class- (follows strategy scope)
keyModeGlobal, 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:

  1. Semantic mismatch: ID identifies an object itself, not how it's referenced. A Person's ID is "John-Doe" regardless of whether accessed via company.ceo or department.employees[0].
  2. Consistency: The same object appearing in multiple references should have the same ID representation.
  3. 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:

json
{
  "_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.

idKeyModeidOnTop (configured)idOnTop (effective)
ID_ONLY / BOTH / FEATURE_ONLYtruetrue
ID_ONLY / BOTH / FEATURE_ONLYfalsefalse
NONEtruefalse (no ID serialized)
NONEfalsefalse

Migration from v1: The old useId=false boolean is replaced by IdKeyMode.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

SettingField OrderExample
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 _id key and the EIDAttribute feature (the eID="true" EAttribute, resolved via eClass.getEIDAttribute()) are promoted to the front of the output. This matches the behaviour of tabular exporters (CSV/ODS/XLSX), where idOnTop floats 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 _id as the first field improves indexing performance
  • idOnTop=false (default): Type information appears first — better for human readability and API conventions

Configuration

Java Builder:

java
CodecConfiguration config = CodecConfiguration.builder()
    .idOnTop(true)  // Put _id before _type
    .build();

EAnnotation (on EClass):

xml
<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:

KeyDescription
_idDefault ID key (recommended)
idCommon alternative (if explicitly configured)

Note: Unlike _type, which has multiple auto-recognized variants, _id is the only default. Using id requires explicit configuration via idKey setting.

9.2 Format Detection

The deserializer auto-detects the format from the JSON structure:

InputDetected Format
"_id": "string"PLAIN
"_id": 123PLAIN (numeric)
"_id": { ... }STRUCTURED

9.3 PLAIN Format Deserialization

For single ID feature:

  1. Read the _id value as string
  2. Convert to the ID attribute's data type
  3. Set on the EObject

For multiple ID features (combined ID):

  1. Read the _id value as string (e.g., "John-Doe")
  2. If _separator field is present, use that separator
  3. Otherwise, use configured separator (default: -)
  4. Split by separator: ["John", "Doe"]
  5. Map to features in order: firstName="John", lastName="Doe"

Example:

json
{
  "_id": "John-Doe-42",
  "_separator": "-"
}

Deserializes to:

  • firstName = "John"
  • lastName = "Doe"
  • sequence = "42" (converted to appropriate type)

9.4 STRUCTURED Format Deserialization

  1. Read the _id object
  2. Map each key to the corresponding feature
  3. Convert values to appropriate data types

Example:

json
{
  "_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:

ScenarioBehavior
_id in JSON, no ID attributeLog INFO, ID value available for URI fragment
_id in JSON, idFeatures configuredUse configured features
No _id in JSON, has ID attributeFeature 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:

json
{
  "_id": "sales_42",
  "myId": {
    "userGroup": "sales",
    "userId": 42
  }
}

The deserializer:

  1. Detects _id is a combined ID from contained object
  2. The contained object (myId) is deserialized normally
  3. The _id value 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:

ScenarioBehaviorSeverity
_id can't be split (wrong part count)Use value as-is for first featureWARNING
_id feature type mismatchAttempt type conversionERROR if conversion fails
STRUCTURED _id missing expected keyUse default value for featureWARNING
_id present but no ID features configuredID available for URI fragmentINFO

Example - Separator mismatch:

json
// 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

SettingProperty KeyDefault Value
Strategycodec.idStrategyID_FIELD
Formatcodec.idFormatPLAIN
ID Keycodec.idKey_id
Value Keycodec.idValueKeyid
Separatorcodec.idSeparator-
Separator Keycodec.idSeparatorKeyseparator
Key Modecodec.idKeyModeID_ONLY
Serialize Separatorcodec.idSeparatorSerializetrue
ID on Topcodec.idOnTopfalse
Scopecodec.idScopeALL
Format Scopecodec.idFormatScopeALL

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 IDPropertyInvalid AtSeverityDescription
ID-V1idStrategyEReferenceERRORID strategy is class-intrinsic (identity doesn't change based on access path)
ID-V2idFeaturesEReferenceERRORID features are class-specific
ID-V3idSeparatorEReferenceERRORSeparator is class-specific
ID-V4idSeparatorKeyEReferenceERRORSeparator config is class-specific
ID-V5idSeparatorSerializeEReferenceERRORSeparator config is class-specific
ID-V6idKeyModeEReferenceERRORKey mode is class-intrinsic
ID-V7idValueKeyEReferenceERRORInner key is class-specific
ID-V8idOnTopEReferenceERRORField ordering is class-specific
ID-V9idValueReaderNameEReferenceERRORValue reader is class-intrinsic
ID-V10idValueWriterNameEReferenceERRORValue writer is class-intrinsic
ID-V11idScopeEAnnotationWARNINGRuntime-only property, ignored if found in EAnnotation
ID-V12idFormatScopeEAnnotationWARNINGRuntime-only property, ignored if found in EAnnotation
ID-V13Any id* keyEAttributeERRORID configuration not applicable to attributes

Note: idFormat and idKey ARE valid on EReference (presentation can vary by context). See 16-annotation-reference.md for the full valid-level matrix.


Next: Reference Serialization →

Released under the EPL-2.0 License. Eclipse Fennec is part of the Eclipse Foundation.