-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support custom data type (de)serialization with EFactory #26
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,7 +16,6 @@ | |
import java.io.IOException; | ||
|
||
import org.eclipse.emf.ecore.EDataType; | ||
import org.eclipse.emf.ecore.EEnum; | ||
import org.eclipse.emf.ecore.util.EcoreUtil; | ||
import org.eclipse.emfcloud.jackson.databind.EMFContext; | ||
|
||
|
@@ -26,6 +25,11 @@ | |
|
||
public class EDataTypeDeserializer extends JsonDeserializer<Object> { | ||
|
||
public static boolean isJavaLangType(final EDataType dataType) { | ||
String instanceClassName = dataType.getInstanceClassName(); | ||
return instanceClassName.startsWith("java.lang.") || instanceClassName.indexOf('.') < 0; | ||
} | ||
|
||
@Override | ||
public Object deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException { | ||
final EDataType dataType = EMFContext.getDataType(ctxt); | ||
|
@@ -35,7 +39,7 @@ public Object deserialize(final JsonParser jp, final DeserializationContext ctxt | |
} | ||
Class<?> type = dataType.getInstanceClass(); | ||
|
||
if (type == null || dataType instanceof EEnum || EJAVA_CLASS.equals(dataType) | ||
if (type == null || (!isJavaLangType(dataType)) || EJAVA_CLASS.equals(dataType) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason you removed the EEnum check for this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It shouldn't really matter, as I guess the default handling of EFactory and current emfjson implementation should be the same. I think the main decision should be made elsewhere and this class always use the EFactory. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the EAnnotation approach this check should be replaced by checking the annotation to see which strategy should be used. |
||
|| EJAVA_OBJECT.equals(dataType)) { | ||
return EcoreUtil.createFromString(dataType, jp.getText()); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,22 +16,27 @@ | |
|
||
import java.io.IOException; | ||
|
||
import com.fasterxml.jackson.core.JsonParseException; | ||
import org.eclipse.emf.ecore.EAttribute; | ||
import org.eclipse.emf.ecore.EDataType; | ||
import org.eclipse.emf.ecore.EObject; | ||
import org.eclipse.emf.ecore.EStructuralFeature; | ||
import org.eclipse.emf.ecore.EcorePackage; | ||
import org.eclipse.emf.ecore.resource.Resource; | ||
import org.eclipse.emfcloud.jackson.databind.EMFContext; | ||
import org.eclipse.emfcloud.jackson.databind.deser.EDataTypeDeserializer; | ||
import org.eclipse.emfcloud.jackson.databind.deser.ReferenceEntries; | ||
import org.eclipse.emfcloud.jackson.databind.deser.ReferenceEntry; | ||
import org.eclipse.emfcloud.jackson.databind.ser.EDataTypeSerializer; | ||
import org.eclipse.emfcloud.jackson.databind.type.FeatureKind; | ||
|
||
import com.fasterxml.jackson.core.JsonGenerator; | ||
import com.fasterxml.jackson.core.JsonParseException; | ||
import com.fasterxml.jackson.core.JsonParser; | ||
import com.fasterxml.jackson.core.JsonToken; | ||
import com.fasterxml.jackson.databind.DeserializationContext; | ||
import com.fasterxml.jackson.databind.JavaType; | ||
import com.fasterxml.jackson.databind.JsonDeserializer; | ||
import com.fasterxml.jackson.databind.JsonMappingException; | ||
import com.fasterxml.jackson.databind.JsonSerializer; | ||
import com.fasterxml.jackson.databind.SerializerProvider; | ||
import com.fasterxml.jackson.databind.ser.impl.UnknownSerializer; | ||
|
@@ -52,12 +57,28 @@ public EObjectFeatureProperty(final EStructuralFeature feature, final JavaType t | |
this.defaultValues = OPTION_SERIALIZE_DEFAULT_VALUE.enabledIn(features); | ||
} | ||
|
||
private static final EDataTypeDeserializer EDATA_TYPE_DESERIALIZER = new EDataTypeDeserializer(); | ||
|
||
private boolean shouldUseEFactory(final EDataType dataType) { | ||
return dataType.isSerializable() && dataType.getEPackage() != EcorePackage.eINSTANCE | ||
&& (!EDataTypeDeserializer.isJavaLangType(dataType)); | ||
} | ||
|
||
private JsonDeserializer<Object> findValueDeserializer(final DeserializationContext ctxt) | ||
throws JsonMappingException { | ||
if ((!feature.isMany()) && feature instanceof EAttribute | ||
&& shouldUseEFactory(((EAttribute) feature).getEAttributeType())) { | ||
return EDATA_TYPE_DESERIALIZER; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd like to avoid using static instances of concrete classes like this as it makes it impossible for adopters to avoid it's usage. At the moment deserializers are registered through the EMFDeserializers class so I think we should use the registration mechanism to retrieve the deserializer we want:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above: Sounds like a good idea! |
||
} | ||
return ctxt.findContextualValueDeserializer(javaType, null); | ||
} | ||
|
||
@Override | ||
@SuppressWarnings({ "checkstyle:cyclomaticComplexity", "checkstyle:fallThrough" }) | ||
public void deserializeAndSet(final JsonParser jp, final EObject current, final DeserializationContext ctxt, | ||
final Resource resource) | ||
throws IOException { | ||
final JsonDeserializer<Object> deserializer = ctxt.findContextualValueDeserializer(javaType, null); | ||
final JsonDeserializer<Object> deserializer = findValueDeserializer(ctxt); | ||
JsonToken token = null; | ||
|
||
if (jp.getCurrentToken() == JsonToken.FIELD_NAME) { | ||
|
@@ -120,11 +141,20 @@ public void deserializeAndSet(final JsonParser jp, final EObject current, final | |
} | ||
} | ||
|
||
private static final EDataTypeSerializer EDATA_TYPE_SERIALIZER = new EDataTypeSerializer(); | ||
|
||
private JsonSerializer<Object> findValueSerializer(final SerializerProvider provider) throws JsonMappingException { | ||
if (feature instanceof EAttribute && shouldUseEFactory(((EAttribute) feature).getEAttributeType())) { | ||
return EDATA_TYPE_SERIALIZER; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd like to avoid using static instances of concrete classes like this as it makes it impossible for adopters to avoid it's usage. At the moment serializers are registered through the EMFSerializers class so I think we should use the registration mechanism to retrieve the serializer we want:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds like a good idea! |
||
} | ||
return provider.findValueSerializer(javaType); | ||
} | ||
|
||
@Override | ||
public void serialize(final EObject bean, final JsonGenerator jg, final SerializerProvider provider) | ||
throws IOException { | ||
if (serializer == null) { | ||
serializer = provider.findValueSerializer(javaType); | ||
serializer = findValueSerializer(provider); | ||
} | ||
|
||
EMFContext.setParent(provider, bean); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like a rather greedy approach since we cannot really guarantee that we support all java.lang.* types.
For instance, the
EcoreUtil.createFromString
call will fail hard if you do not provide the necessary create/convert function in the factory.Also having this check as a static method makes it impossible to override this in any sub-class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you define a data type, it is your duty (contract to be fulfilled) to implement the create/convert methods if you need to support serialisation. That's part of the job when using EMF. Hence, IMO (as detailed below) we should be even more greedy. I'm not sure why the original implementation doesn't rely on EFactory in general, I'd assume that all pre-defined data types in Ecore support serialization and just should work. However, the tests would need to be re-written, since there are assumptions there of the specific format that may not match Ecore's. Some cases won't work, e.g. Object[] is handled not by a general serialization of Objects, but using JSON-specific logic, if I understand it correctly. I would start by reverting the logic, use EFactory in all cases where the data type has the serializable flag set and then see if there are special cases that could need special handling, e.g. when a data type wraps an array of something that can be serialized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to make certain data types easier to support, we can also define a separate Emfjson model with pre-defined data types, so anyone can import it and use its data types. This can be cleaner than hard-coding the logic in the serialization class.
An orthogonal option is to introduce some annotations on the data type, for controlling how emfjson-specific logic should be used for serialization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are also not sure why the original implementation did not rely on EFactory in general but I think it is the right way forward. However, as to not break backwards compatibility, we should use an EAnnotation to allow switching between strategies as there might be some customizations we are not aware of. There is already little support for annotations in
JsonAnnotations
.