Custom Value Readers/Writers
← Load/Save Options | Next: Error Handling →
See also:
- Annotation Reference (Feature Configuration) for
valueReaderNameandvalueWriterNamekeys- Load/Save Options for
CODEC_FEATURE_VALUE_READER_INSTANCESandCODEC_FEATURE_VALUE_WRITER_INSTANCES
Extensibility hooks for custom serialization logic.
1. Overview
Custom value readers/writers allow you to:
- Transform values during serialization/deserialization
- Handle special data types (dates, binary, custom formats)
- Implement domain-specific encoding
- Customize reference URI formats
- Convert embedded formats (e.g., JSON Schema to EPackage)
1.1 Interface Hierarchy
The system provides a type-safe interface hierarchy:
CodecValueReader<T, F extends EStructuralFeature>
├── AttributeValueReader<T> extends CodecValueReader<T, EAttribute>
└── ReferenceValueReader<T> extends CodecValueReader<T, EReference>
where T extends EObject
CodecValueWriter<T, F extends EStructuralFeature>
├── AttributeValueWriter<T> extends CodecValueWriter<T, EAttribute>
└── ReferenceValueWriter<T> extends CodecValueWriter<T, EReference>
where T extends EObject1.2 Use Cases by Interface
| Interface | Use Case | Example |
|---|---|---|
AttributeValueReader<T> | Transform primitive/data type values | ISO 8601 date parsing |
AttributeValueWriter<T> | Format primitive/data type values | Base64 encoding |
ReferenceValueReader<T> | Read references in custom format (containment or non-containment) | JSON Schema → EPackage |
ReferenceValueWriter<T> | Write references in custom format (containment or non-containment) | EPackage → JSON Schema |
CodecValueReader<String, EReference> | Transform non-containment reference URIs | MongoDB ObjectId → EMF URI |
CodecValueWriter<EObject, EReference> | Write non-containment reference URIs | Custom URI scheme |
1.3 Core Components
| Component | Purpose |
|---|---|
CodecValueWriter<T, F> | Base interface for custom serialization |
CodecValueReader<T, F> | Base interface for custom deserialization |
AttributeValueWriter<T> | Specialized for EAttribute with canHandle() |
AttributeValueReader<T> | Specialized for EAttribute with canHandle() |
ReferenceValueWriter<T> | Specialized for containment EReference with canHandle() |
ReferenceValueReader<T> | Specialized for containment EReference with canHandle() |
CodecValueRegistry | Registry to store named readers/writers |
2. Interface Architecture
2.1 Base Interfaces
public interface CodecValueReader<T, F extends EStructuralFeature> {
/**
* Returns the unique name for this reader.
* Used for auto-registration in CodecValueRegistry.
*/
String getName();
/**
* Reads a value from the parser context.
*/
T read(CodecReaderContext ctx, F feature) throws IOException;
}
public interface CodecValueWriter<T, F extends EStructuralFeature> {
/**
* Returns the unique name for this writer.
* Used for auto-registration in CodecValueRegistry.
*/
String getName();
/**
* Writes a value to the generator context.
*/
void write(T value, F feature, CodecWriterContext ctx) throws IOException;
}2.2 Context Interfaces
The context interfaces provide access to the codec configuration, Jackson contexts, and diagnostics:
public interface CodecReaderContext {
/** The Jackson parser for reading JSON tokens */
JsonParser getParser();
/** The Jackson deserialization context */
DeserializationContext getJacksonContext();
/** The effective codec configuration (merged from all sources) */
EffectiveCodecConfig getConfig();
/** Collector for warnings and errors */
DiagnosticCollector getDiagnostics();
/** Convenience: add a warning diagnostic */
void addWarning(String message);
/** Convenience: add an error diagnostic */
void addError(String message);
}
public interface CodecWriterContext {
/** The Jackson generator for writing JSON tokens */
JsonGenerator getGenerator();
/** The Jackson serialization context */
SerializationContext getJacksonContext();
/** The effective codec configuration (merged from all sources) */
EffectiveCodecConfig getConfig();
/** Collector for warnings and errors */
DiagnosticCollector getDiagnostics();
/** Convenience: add a warning diagnostic */
void addWarning(String message);
/** Convenience: add an error diagnostic */
void addError(String message);
}Why provide EffectiveCodecConfig?
Custom readers/writers often need configuration context:
- A date writer might check a global date format setting
- A reference writer might need to know if smart compression is enabled
- A custom type writer might need the configured
typeStrategy
Why provide DiagnosticCollector?
Custom readers/writers should report problems through the standard diagnostic mechanism:
- Missing required fields → warning or error
- Invalid format → warning with fallback or error
- Deprecated format detected → warning
2.3 Specialized Attribute Interfaces
public interface AttributeValueReader<T> extends CodecValueReader<T, EAttribute> {
/**
* Checks if this reader can handle the given attribute.
*/
boolean canHandle(EAttribute attribute);
}
public interface AttributeValueWriter<T> extends CodecValueWriter<T, EAttribute> {
/**
* Checks if this writer can handle the given attribute.
*/
boolean canHandle(EAttribute attribute);
}2.4 Specialized Reference Interfaces
public interface ReferenceValueReader<T extends EObject>
extends CodecValueReader<T, EReference> {
/**
* Checks if this reader can handle the given reference.
* Typically checks if the reference type is compatible.
*/
boolean canHandle(EReference reference);
}
public interface ReferenceValueWriter<T extends EObject>
extends CodecValueWriter<T, EReference> {
/**
* Checks if this writer can handle the given reference.
* Typically checks if the reference type is compatible.
*/
boolean canHandle(EReference reference);
}2.5 The canHandle() Method
The canHandle() method enables type-safe validation at construction time:
public class EPackageValueReader implements ReferenceValueReader<EPackage> {
@Override
public String getName() {
return "jsonSchemaToEPackage";
}
@Override
public boolean canHandle(EReference reference) {
// Only handle references of type EPackage
return EcorePackage.Literals.EPACKAGE.isSuperTypeOf(
reference.getEReferenceType());
}
@Override
public EPackage read(CodecReaderContext ctx, EReference ref) throws IOException {
// Convert JSON Schema to EPackage
// Access config if needed: ctx.getConfig().getTypeStrategy()
TreeNode tree = ctx.getParser().readValueAsTree();
return converter.convert((JsonNode) tree, null);
}
}Benefits:
- Early validation: Incompatible readers/writers are rejected at construction
- Clear error messages: Log warnings when reader/writer cannot handle a feature
- Type safety: Prevents runtime ClassCastException
3. Attribute Value Readers/Writers
3.1 Purpose
Transform attribute values during serialization/deserialization:
- Custom date/time formats
- Binary encoding (Base64, hex)
- Domain-specific value transformations
3.2 Example: Date Formatting
public class ISODateReader implements AttributeValueReader<Date> {
private final SimpleDateFormat sdf;
public ISODateReader() {
sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
}
@Override
public String getName() {
return "isoDate";
}
@Override
public boolean canHandle(EAttribute attribute) {
return attribute.getEAttributeType().getInstanceClass() == Date.class;
}
@Override
public Date read(CodecReaderContext ctx, EAttribute attr) throws IOException {
try {
return sdf.parse(ctx.getParser().getString());
} catch (ParseException e) {
ctx.addWarning("Invalid date format: " + e.getMessage());
return null; // Or throw IOException for strict mode
}
}
}
public class ISODateWriter implements AttributeValueWriter<Date> {
private final SimpleDateFormat sdf;
public ISODateWriter() {
sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
}
@Override
public String getName() {
return "isoDate";
}
@Override
public boolean canHandle(EAttribute attribute) {
return attribute.getEAttributeType().getInstanceClass() == Date.class;
}
@Override
public void write(Date date, EAttribute attr, CodecWriterContext ctx)
throws IOException {
ctx.getGenerator().writeString(sdf.format(date));
}
}3.3 Example: Using EffectiveConfig
Custom readers/writers can access the codec configuration:
public class ConfigAwareDateWriter implements AttributeValueWriter<Date> {
@Override
public String getName() {
return "configAwareDate";
}
@Override
public boolean canHandle(EAttribute attribute) {
return attribute.getEAttributeType().getInstanceClass() == Date.class;
}
@Override
public void write(Date date, EAttribute attr, CodecWriterContext ctx)
throws IOException {
// Access configuration to determine format
EffectiveCodecConfig config = ctx.getConfig();
// Example: check a hypothetical dateFormat setting
String format = config.getDateFormat().orElse("yyyy-MM-dd'T'HH:mm:ss'Z'");
SimpleDateFormat sdf = new SimpleDateFormat(format);
ctx.getGenerator().writeString(sdf.format(date));
}
}3.4 Example: Lambda Style with Explicit Name
For simple cases, use the builder with an explicit name:
// Register via builder with explicit name (since lambdas can't implement getName())
CodecConfiguration.builder()
.valueWriter("base64", (value, attr, ctx) ->
ctx.getGenerator().writeString(
Base64.getEncoder().encodeToString((byte[]) value)))
.valueReader("base64", (ctx, attr) ->
Base64.getDecoder().decode(ctx.getParser().getString()))
.build();3.4 Serialization Flow
AttributeSerializationEntry
│
├─ Lookup customWriter from registry
│
├─ If writer instanceof AttributeValueWriter:
│ │
│ ├─ Check canHandle(attribute)
│ │ │
│ │ ├─ YES: Use this writer
│ │ │
│ │ └─ NO: Log warning, fall back to default
│
├─ If customWriter available:
│ customWriter.write(value, attribute, gen, ctxt)
│
└─ Otherwise: Default serialization4. Reference Value Readers/Writers
4.1 Two Types of Reference Customization
| Type | Interface | Purpose | When Used |
|---|---|---|---|
| Inline Object | ReferenceValueReader<T> / ReferenceValueWriter<T> | Convert entire object structure (containment or non-containment) | JSON Schema ↔ EPackage, JSON Schema ↔ EClass |
| Non-Containment URI | CodecValueReader<String, EReference> / CodecValueWriter<EObject, EReference> | Transform reference URI | MongoDB ObjectId ↔ EMF URI |
4.2 Inline Object Reference Readers/Writers
Used for references (containment or non-containment) where the referenced object needs custom inline conversion. When a ReferenceValueWriter/ReferenceValueReader is configured via valueWriterName/valueReaderName, it is used regardless of the reference's containment flag — the annotation is an explicit user intent to serialize the object inline in a custom format.
Example: JSON Schema to EPackage (OpenAPI)
public class EPackageValueReader implements ReferenceValueReader<EPackage> {
private final JsonSchemaToEPackageConverter converter;
public EPackageValueReader(JsonSchemaToEPackageConverter converter) {
this.converter = converter;
}
@Override
public String getName() {
return "jsonSchemaToEPackage";
}
@Override
public boolean canHandle(EReference reference) {
return EcorePackage.Literals.EPACKAGE.isSuperTypeOf(
reference.getEReferenceType());
}
@Override
public EPackage read(CodecReaderContext ctx, EReference ref) throws IOException {
TreeNode tree = ctx.getParser().readValueAsTree();
EPackage result = converter.convert((JsonNode) tree, null);
if (result == null) {
ctx.addWarning("Failed to convert JSON Schema to EPackage");
}
return result;
}
}
public class EPackageValueWriter implements ReferenceValueWriter<EPackage> {
private final EPackageToJsonSchemaConverter converter;
public EPackageValueWriter(EPackageToJsonSchemaConverter converter) {
this.converter = converter;
}
@Override
public String getName() {
return "ePackageToJsonSchema";
}
@Override
public boolean canHandle(EReference reference) {
return EcorePackage.Literals.EPACKAGE.isSuperTypeOf(
reference.getEReferenceType());
}
@Override
public void write(EPackage value, EReference ref, CodecWriterContext ctx)
throws IOException {
JsonNode schema = converter.convert(value);
ctx.getGenerator().writePOJO(schema);
}
}Serialization Flow
ReferenceSerializationEntry
│
├─ Lookup customWriter from registry (valueWriterName)
│
├─ If writer instanceof ReferenceValueWriter:
│ │
│ ├─ Check canHandle(reference)
│ │ │
│ │ ├─ YES: store as referenceWriter
│ │ │
│ │ └─ NO: Log warning, use default serialization
│
├─ At serialization time:
│ ├─ Containment + referenceWriter → referenceWriter.write(...)
│ ├─ Containment + no writer → ctxt.writeValue(gen, target)
│ ├─ Non-containment + referenceWriter → referenceWriter.write(...)
│ └─ Non-containment + no writer → writeReferenceObject (URI/$ref)
│
└─ Key: referenceWriter is used for BOTH containment and non-containment4.3 Non-Containment Reference URI Customization
Used for non-containment references to transform URI format.
Example: MongoDB ObjectId
public class MongoIdWriter implements CodecValueWriter<EObject, EReference> {
@Override
public String getName() {
return "mongoId";
}
@Override
public void write(EObject target, EReference ref, CodecWriterContext ctx)
throws IOException {
Object id = target.eGet(target.eClass().getEStructuralFeature("_id"));
String value = id != null ? id.toString() :
target.eResource().getURIFragment(target);
ctx.getGenerator().writeString(value);
}
}
public class MongoIdReader implements CodecValueReader<String, EReference> {
@Override
public String getName() {
return "mongoId";
}
@Override
public String read(CodecReaderContext ctx, EReference ref) throws IOException {
String objectId = ctx.getParser().getString();
return "#/persons/" + objectId; // Transform to EMF URI
}
}Serialization Flow (Non-Containment)
ReferenceSerializationEntry (non-containment)
│
├─ writeReferenceObject()
│ │
│ ├─ Write _type
│ │
│ ├─ If uriWriter configured:
│ │ uriWriter.write(target, reference, gen, ctxt)
│ │
│ └─ Default: gen.writeString(getReferenceUri())5. Registration
There are two ways to register value readers/writers:
- Instance-based registration via builder (recommended) - uses
getName()for auto-registration - Direct registry manipulation - explicit name-based registration
5.1 Instance-Based Registration via Builder (Recommended)
The builder accepts reader/writer instances and uses getName() for auto-registration:
CodecConfiguration config = CodecConfiguration.builder()
// Single instances - getName() used as registry key
.valueReader(new ISODateReader()) // registered as "isoDate"
.valueWriter(new ISODateWriter()) // registered as "isoDate"
.valueReader(new EPackageValueReader()) // registered as "jsonSchemaToEPackage"
.valueWriter(new EPackageValueWriter()) // registered as "ePackageToJsonSchema"
// Multiple instances at once (varargs)
.valueReaders(new MongoIdReader(), new CustomReader1(), new CustomReader2())
.valueWriters(new MongoIdWriter(), new CustomWriter1())
// Lambda/anonymous with explicit name (getName() not available)
.valueReader("base64", (ctx, attr) ->
Base64.getDecoder().decode(ctx.getParser().getString()))
.valueWriter("base64", (value, attr, ctx) ->
ctx.getGenerator().writeString(Base64.getEncoder().encodeToString((byte[]) value)))
.build();Benefits of instance-based registration:
- Type-safe: compiler ensures reader/writer implements the interface
- Self-documenting:
getName()provides consistent naming - No string literals: reader/writer owns its name
- Easier testing: instances can be mocked
5.2 Direct Registry Manipulation
For programmatic control, use CodecValueRegistry directly:
CodecValueRegistry registry = new CodecValueRegistry();
// Register with explicit name
registry.register(new ISODateReader()); // uses reader.getName()
registry.register(new ISODateWriter()); // uses writer.getName()
// Or with override name
registry.registerReader("customDate", new ISODateReader());
registry.registerWriter("customDate", new ISODateWriter());5.3 Type Resolution at Construction Time
When a reader/writer is resolved from the registry:
// In ReferenceDeserializationEntry constructor:
CodecValueReader<?, ?> reader = registry.getReader(readerName).orElse(null);
if (reader instanceof ReferenceValueReader<?> refReader) {
// For containment references
if (refReader.canHandle(reference)) {
this.containmentReader = refReader;
} else {
LOGGER.warning("Reader cannot handle reference: " + reference.getName());
}
} else if (reader != null) {
// For non-containment URI transformation
this.uriReader = (CodecValueReader<String, EReference>) reader;
}5.4 Passing Registry to CodecResource
The registry is typically obtained from the configuration:
CodecConfiguration config = CodecConfiguration.builder()
.valueReader(new ISODateReader())
.valueWriter(new ISODateWriter())
.build();
// Registry is accessible from config
CodecValueRegistry registry = config.getValueRegistry();
// Or pass explicitly to CodecResource
CodecResource resource = new CodecResource(
uri,
metadataService,
config,
config.getValueRegistry(),
mapperBuilder
);6. Activation per Feature
6.1 EAnnotation on EAttribute
<eStructuralFeatures xsi:type="ecore:EAttribute" name="createdAt" eType="...">
<eAnnotations source="http://eclipse.org/fennec/codec">
<details key="valueWriterName" value="isoDate"/>
<details key="valueReaderName" value="isoDate"/>
</eAnnotations>
</eStructuralFeatures>6.2 EAnnotation on Containment EReference
<eStructuralFeatures xsi:type="ecore:EReference" name="schemas"
eType="ecore:EClass http://www.eclipse.org/emf/2002/Ecore#//EPackage"
containment="true">
<eAnnotations source="http://eclipse.org/fennec/codec">
<details key="valueWriterName" value="schemas"/>
<details key="valueReaderName" value="schemas"/>
</eAnnotations>
</eStructuralFeatures>6.3 EAnnotation on Non-Containment EReference
<eStructuralFeatures xsi:type="ecore:EReference" name="manager" eType="#//Person">
<eAnnotations source="http://eclipse.org/fennec/codec">
<details key="valueWriterName" value="mongoId"/>
<details key="valueReaderName" value="mongoId"/>
</eAnnotations>
</eStructuralFeatures>6.4 Runtime Override via Load/Save Options
Option A: Reference by name (reader/writer must be in registry)
Map<String, Object> options = Map.of(
"codec.featureValueReaders", Map.of(
MyPackage.Literals.PERSON__CREATED_AT, "isoDate"
),
"codec.featureValueWriters", Map.of(
MyPackage.Literals.PERSON__CREATED_AT, "isoDate"
)
);
resource.load(inputStream, options);Option B: Direct instance binding (no registry lookup)
Map<String, Object> options = Map.of(
"codec.featureValueReaderInstances", Map.of(
MyPackage.Literals.PERSON__CREATED_AT, new ISODateReader()
),
"codec.featureValueWriterInstances", Map.of(
MyPackage.Literals.PERSON__CREATED_AT, new ISODateWriter()
)
);
resource.save(outputStream, options);Option C: Builder API
Map<String, Object> options = CodecOptionsBuilder.create()
// By name
.featureValueReader(MyPackage.Literals.PERSON__CREATED_AT, "isoDate")
.featureValueWriter(MyPackage.Literals.PERSON__CREATED_AT, "isoDate")
// Or by instance
.featureValueReaderInstance(MyPackage.Literals.PERSON__CREATED_AT, new ISODateReader())
.featureValueWriterInstance(MyPackage.Literals.PERSON__CREATED_AT, new ISODateWriter())
.build();7. Null Handling
7.1 Attributes
Custom readers/writers are not called for null values:
- Null attribute values are serialized as JSON
null - JSON
nullis deserialized asnullwithout calling reader
7.2 References
Custom readers/writers are not called for null references:
- Null references are serialized according to
serializeNullconfig - JSON
nullsets the reference tonullwithout calling reader
8. Error Handling
8.1 Writer Errors
If a custom writer throws an IOException, it is wrapped:
throw new UncheckedIOException(
"Custom value writer failed for " + feature.getName(), e);8.2 Reader Errors
If a custom reader throws an IOException, it is wrapped:
throw new UncheckedIOException(
"Custom value reader failed for " + feature.getName(), e);8.3 canHandle() Returns False
If a specialized reader/writer's canHandle() returns false:
- A warning is logged
- Fall back to default serialization/deserialization
- No error is thrown
8.4 Missing Writer/Reader
If a configured name is not found in the registry:
- Fall back to default serialization/deserialization
- No error is thrown (silent fallback)
9. Registry API
9.1 Registration
// Auto-registration using getName()
CodecValueRegistry register(CodecValueReader<?, ?> reader);
CodecValueRegistry register(CodecValueWriter<?, ?> writer);
// Explicit name (overrides getName())
CodecValueRegistry registerReader(String name, CodecValueReader<?, ?> reader);
CodecValueRegistry registerWriter(String name, CodecValueWriter<?, ?> writer);
// Bulk registration
CodecValueRegistry registerAll(CodecValueReader<?, ?>... readers);
CodecValueRegistry registerAll(CodecValueWriter<?, ?>... writers);9.2 Lookup
Optional<CodecValueWriter<?, ?>> getWriter(String name);
Optional<CodecValueReader<?, ?>> getReader(String name);9.3 Introspection
boolean hasWriter(String name);
boolean hasReader(String name);
Map<String, CodecValueWriter<?, ?>> getWriters();
Map<String, CodecValueReader<?, ?>> getReaders();
Set<String> getWriterNames();
Set<String> getReaderNames();10. Configuration Hierarchy
Custom value reader/writer resolution follows the standard configuration hierarchy:
- Load/Save Options (highest priority)
codec.featureValueReaderInstances/codec.featureValueWriterInstances(direct instance)codec.featureValueReaders/codec.featureValueWriters(by name)
- ResourceFactory Defaults
- CodecConfiguration
- Instances registered via
.valueReader()/.valueWriter()
- Instances registered via
- EAnnotation on EStructuralFeature
valueReaderName/valueWriterName(by name)
- Built-in Defaults (no custom reader/writer)
10.1 Builder Configuration Options
| Builder Method | Description |
|---|---|
.valueReader(reader) | Register reader using reader.getName() |
.valueWriter(writer) | Register writer using writer.getName() |
.valueReaders(r1, r2, ...) | Register multiple readers (varargs) |
.valueWriters(w1, w2, ...) | Register multiple writers (varargs) |
.valueReader(name, lambda) | Register lambda reader with explicit name |
.valueWriter(name, lambda) | Register lambda writer with explicit name |
10.2 Load/Save Option Keys
See also: Load/Save Options for complete option reference.
Per-Feature Binding
Bind readers/writers to specific features:
| Option Key | Type | Description |
|---|---|---|
codec.featureValueReaderInstances | Map<EStructuralFeature, CodecValueReader> | Reader instances per feature (direct binding, bypasses registry) |
codec.featureValueWriterInstances | Map<EStructuralFeature, CodecValueWriter> | Writer instances per feature (direct binding, bypasses registry) |
codec.featureValueReaders | Map<EStructuralFeature, String> | Deprecated — use config resolution (valueReaderName) or instances |
codec.featureValueWriters | Map<EStructuralFeature, String> | Deprecated — use config resolution (valueWriterName) or instances |
Instance binding works for EAttributes and EReferences alike (see Load/Save Options §4). To bind a registered reader/writer by name per operation, use the general config resolution instead ("ClassName.featureName" → valueReaderName/valueWriterName).
Example:
Map<String, Object> options = Map.of(
// Bind an instance directly (bypasses registry)
"codec.featureValueReaderInstances", Map.of(
MyPackage.Literals.PERSON__UPDATED_AT, new ISODateReader()
),
// Bind a registered reader by name via config resolution
"Person.createdAt", Map.of("valueReaderName", "isoDate")
);Note — no runtime registration: there is deliberately no option to register readers/writers into the
CodecValueRegistryper load/save operation (the formercodec.valueReaders/codec.valueWritersoptions were never implemented and have been removed, see issue #45). Readers/writers resolved by name — including those referenced byvalueReaderName/valueWriterNamemodel annotations — must be present in the registry when the resource is created.
11. Implementation Classes
| Class | Purpose |
|---|---|
CodecValueReader<T, F> | Base interface for custom deserialization with getName() |
CodecValueWriter<T, F> | Base interface for custom serialization with getName() |
CodecReaderContext | Context wrapper with parser, config, diagnostics |
CodecWriterContext | Context wrapper with generator, config, diagnostics |
AttributeValueReader<T> | Specialized for EAttribute with canHandle() |
AttributeValueWriter<T> | Specialized for EAttribute with canHandle() |
ReferenceValueReader<T> | Specialized for containment EReference with canHandle() |
ReferenceValueWriter<T> | Specialized for containment EReference with canHandle() |
CodecValueRegistry | Named reader/writer storage with auto-registration |
AttributeSerializationEntry | Invokes attribute writers |
AttributeDeserializationEntry | Invokes attribute readers |
ReferenceSerializationEntry | Invokes reference writers (both types) |
ReferenceDeserializationEntry | Invokes reference readers (both types) |
12. Real-World Example: OpenAPI with JSON Schema
OpenAPI documents embed JSON Schema in components/schemas. The codec handles this by converting between EPackage and JSON Schema using custom ReferenceValueReader/Writer.
12.1 Quick Example
<!-- In openapi.ecore -->
<eStructuralFeatures xsi:type="ecore:EReference" name="schemas"
eType="ecore:EClass http://www.eclipse.org/emf/2002/Ecore#//EPackage"
containment="true">
<eAnnotations source="http://eclipse.org/fennec/codec">
<details key="valueReaderName" value="jsonSchemaToEPackage"/>
<details key="valueWriterName" value="ePackageToJsonSchema"/>
</eAnnotations>
</eStructuralFeatures>// Register converters via builder (uses getName() for auto-registration)
CodecConfiguration config = CodecConfiguration.builder()
.valueReader(new EPackageValueReader(converter)) // "jsonSchemaToEPackage"
.valueWriter(new EPackageValueWriter(converter)) // "ePackageToJsonSchema"
.build();{
"openapi": "3.0.3",
"components": {
"schemas": {
"Person": {
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer" }
}
}
}
}
}The schemas object is automatically converted to an EPackage with an EClass named "Person".
For complete OpenAPI support documentation, see Chapter 17: OpenAPI Support.
