-
Notifications
You must be signed in to change notification settings - Fork 15
Customization
The emfjson-jackson module allows several customizations to the JSON format it can handle. It is possible to customization the reserved fields names for types, ids and references. It is also possible to customize how the module will resolve an EClass or a reference from a JSON content.
The type field is use to determine the EClass of the object. By default the field is named eClass
and it's value is the URI of the EClass. The URI is in general the EClass package nsURI follow by a fragment being the EClass name. This URI is used by the resourceSet to locate the resource containing the package definition.
The field name can be change by configuring the EMF module. The configuration is done by using a EcoreTypeInfo.
import org.emfjson.jackson.annotations.EcoreTypeInfo;
ObjectMapper mapper = new ObjectMapper();
EMFModule module = new EMFModule();
module.setTypeInfo(new EcoreTypeInfo("type"));
mapper.registerModule(module);
Once this is done, all objects will now contain a type
field instead of a eClass
field. The value of that field is still the EClass URI.
{
"type": "http://emfjson.org/domain#//User",
"id": 1,
"name": "Bob"
}
To change the value of the type field, it is necessary to tell the module to use a specific ValueWriter
. This is done by passing a second argument to the EcoreTypeInfo
.
In this example, the ValueWriter
will return the EClass name instead of it's URI.
module.setTypeInfo(new EcoreTypeInfo("type",
new ValueWriter<EClass, String>() {
@Override
public String writeValue(EClass value, SerializerProvider context) {
return value.getName();
}
}));
The JSON output will now be like this
{
"type": "User",
"id": 1,
"name": "Bob"
}
It is also possible to parse custom type values by specifying a ValueReader
. This reader will take as input a string and return the EClass corresponding to that string.
In this example, we assume that the values of the type field is the name of the EClass. In that case we return the EClass from our domain package that matches that name.
module.setTypeInfo(new EcoreTypeInfo("type",
new ValueReader<String, EClass>() {
public EClass readValue(String value, DeserializationContext context) {
return (EClass) ModelPackage.eINSTANCE.getEClassifier(value);
}
}));
When customizing both the serialization and deserialization of a type field, the valueReader and valueWriter
have to be set to the same EcoreTypeInfo
.
module.setTypeInfo(new EcoreTypeInfo("type", valueReader, valueWriter));
The id field is used to uniquely identify an object inside a resource. The id field is in general only use when the option OPTION_USE_ID
is set.
To customize this field, it is necessary to tell the module to use a custom EcoreIdentityInfo
. In the following example we tell the module to serialize all id fields as _id
.
EMFModule module = new EMFModule();
module.configure(EMFModule.Feature.OPTION_USE_ID, true);
module.configure(EMFModule.Feature.OPTION_SERIALIZE_TYPE, false);
module.setIdentityInfo(new EcoreIdentityInfo("_id"));
mapper.registerModule(module);
This will result in that output. Note that in that case, there are no type field because we have set the option
OPTION_SERIALIZE_TYPE
to false.
{
"_id": 1,
"name": "Bob"
}
It is possible to use a custom serializer for ids. In that case we tell the module to use a specific ValueWriter
for ids. This writer takes as input an object and return a value. That value can be of any type.
module.setIdentityInfo(new EcoreIdentityInfo("_id",
new ValueWriter<EObject, Object>() {
@Override
public Object writeValue(EObject value, SerializerProvider context) {
return 1;
}
}));
The output will be
{
"_id": 1,
"name": "Bob"
}
To deserialize custom id values, it is necessary to tell the module to use a custom ValueReader
for ids. This reader will take as input a value (can be of any type) and should return a String.
module.setIdentityInfo(new EcoreIdentityInfo("_id",
new ValueReader<Object, String>() {
@Override
public String readValue(Object value, DeserializationContext context) {
return value.toString();
}
}));
References are by default serialized as JSON objects that contain two fields. The first field is the type of the referenced object and the second field is the URI of the referenced object. The type field is named eClass
and the URI field is named $ref
.
Here is an example of references as serialize in JSON
{
"eClass": "http://emfjson.org/domain#//User",
"name": "Bob",
"friends": [ {
"eClass": "http://emfjson.org/domain#//User",
"$ref":"src/main/resources/data2.json#/"
} ]
}
It is possible to fully customize how references are serialize and deserialize, meaning that it is not only possible to customize the field names but also add more fields to the reference field or use simple values instead of objects to represent references.
To customize the field names, it is necessary to tell the module to use a special EcoreReferenceInfo
. The latter will take two arguments. The first being the name to use for the URI field and the second being the name to use for the type field.
Here we tell the module to use my_ref
instead of $ref
and to use my_type
instead of eClass
.
EMFModule module = new EMFModule();
module.setReferenceInfo(new EcoreReferenceInfo.Base("my_ref", "my_type"));
mapper.registerModule(module);
This will give us such output
{
"eClass": "http://emfjson.org/domain#//User",
"name": "Bob",
"friends": [ {
"my_type": "http://emfjson.org/domain#//User",
"my_ref":"src/main/resources/data2.json#/"
} ]
}
It is also possible to use a custom serializer for references. This should be use when you want to fully control how references are serialize.
The serializer is register directly to the module. It takes as input an EObject
, that is the object that is referenced. It is then up to you to use the JsonGenerator
to decide how the reference should be serialize.
In this example we decided to serialize the reference as a simple string. The string being the id of the object.
module.setReferenceSerializer(new JsonSerializer<EObject>() {
@Override
public void serialize(EObject v, JsonGenerator g, SerializerProvider s)
throws IOException {
g.writeString(((JsonResource) v.eResource()).getID(v));
}
});
We will then have an output like this
{
"eClass": "http://emfjson.org/domain#//User",
"name": "Bob",
"friends": [ "2" ]
}
When using custom serializer for references, it is necessary to tell the module how to parse those. This can be done by using a custom reference deserializer.
The deserializer is register directly to the module. It is a standard Jackson deserializer that expects as output a ReferenceEntry
. A ReferenceEntry
is an object that contains the necessary information needed by the module to locate and instantiate an EObject.
In the following example, we assume that references are strings that contain ids of referenced objects. The deserializer reads the current reference id by calling parser.getText()
and creates a ReferenceEntry
.
module.setReferenceDeserializer(new JsonDeserializer<ReferenceEntry>() {
@Override
public ReferenceEntry deserialize(JsonParser parser, DeserializationContext ctxt)
throws IOException {
final EObject parent = EMFContext.getParent(ctxt);
final EReference reference = EMFContext.getReference(ctxt);
if (parser.getCurrentToken() == JsonToken.FIELD_NAME) {
parser.nextToken();
}
return new ReferenceEntry.Base(parent, reference, parser.getText());
}
});