From 86f7da9e836e2484bea54cbde51e08bf7a5b642b Mon Sep 17 00:00:00 2001 From: Hallvard Traetteberg Date: Tue, 22 Feb 2022 00:15:44 +0100 Subject: [PATCH] Support custom data type (de)serialization with EFactory with LocalDate as example --- .../databind/deser/EDataTypeDeserializer.java | 8 +++-- .../property/EObjectFeatureProperty.java | 36 +++++++++++++++++-- .../emfcloud/jackson/tests/ValueTest.java | 33 +++++++++++++++++ src/test/resources/model/model.xcore | 10 ++++++ 4 files changed, 82 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/eclipse/emfcloud/jackson/databind/deser/EDataTypeDeserializer.java b/src/main/java/org/eclipse/emfcloud/jackson/databind/deser/EDataTypeDeserializer.java index 9799c5c..2a776c9 100755 --- a/src/main/java/org/eclipse/emfcloud/jackson/databind/deser/EDataTypeDeserializer.java +++ b/src/main/java/org/eclipse/emfcloud/jackson/databind/deser/EDataTypeDeserializer.java @@ -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 { + 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) || EJAVA_OBJECT.equals(dataType)) { return EcoreUtil.createFromString(dataType, jp.getText()); } diff --git a/src/main/java/org/eclipse/emfcloud/jackson/databind/property/EObjectFeatureProperty.java b/src/main/java/org/eclipse/emfcloud/jackson/databind/property/EObjectFeatureProperty.java index fc238c3..1d50f7b 100755 --- a/src/main/java/org/eclipse/emfcloud/jackson/databind/property/EObjectFeatureProperty.java +++ b/src/main/java/org/eclipse/emfcloud/jackson/databind/property/EObjectFeatureProperty.java @@ -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 findValueDeserializer(final DeserializationContext ctxt) + throws JsonMappingException { + if ((!feature.isMany()) && feature instanceof EAttribute + && shouldUseEFactory(((EAttribute) feature).getEAttributeType())) { + return EDATA_TYPE_DESERIALIZER; + } + 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 deserializer = ctxt.findContextualValueDeserializer(javaType, null); + final JsonDeserializer 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 findValueSerializer(final SerializerProvider provider) throws JsonMappingException { + if (feature instanceof EAttribute && shouldUseEFactory(((EAttribute) feature).getEAttributeType())) { + return EDATA_TYPE_SERIALIZER; + } + 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); diff --git a/src/test/java/org/eclipse/emfcloud/jackson/tests/ValueTest.java b/src/test/java/org/eclipse/emfcloud/jackson/tests/ValueTest.java index fc41991..4fd4feb 100755 --- a/src/test/java/org/eclipse/emfcloud/jackson/tests/ValueTest.java +++ b/src/test/java/org/eclipse/emfcloud/jackson/tests/ValueTest.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; +import java.time.LocalDate; import java.util.Calendar; import java.util.Date; import java.util.List; @@ -401,4 +402,36 @@ public void testSaveObjectTypeValue() { assertThat(result.get("objectType").isNumber()).isTrue(); } + + @Test + public void testLocalDateValue() { + Resource resource = resourceSet.createResource(URI.createURI("tests/test.json")); + + ETypes valueObject = ModelFactory.eINSTANCE.createETypes(); + valueObject.setELocalDate(LocalDate.of(2021, 9, 13)); + resource.getContents().add(valueObject); + + JsonNode result = mapper.valueToTree(resource); + + assertEquals("2021-09-13", result.get("eLocalDate").asText()); + } + + @Test + public void testLoadLocalDateValue() throws IOException { + JsonNode data = mapper.createObjectNode() + .put("eClass", "http://www.emfjson.org/jackson/model#//ETypes") + .put("eLocalDate", "2021-09-13"); + + Resource resource = resourceSet.createResource(URI.createURI("tests/test.json")); + resource.load(new ByteArrayInputStream(mapper.writeValueAsBytes(data)), null); + + assertEquals(1, resource.getContents().size()); + + EObject root = resource.getContents().get(0); + assertEquals(ModelPackage.Literals.ETYPES, root.eClass()); + + LocalDate value = ((ETypes) root).getELocalDate(); + + assertEquals(LocalDate.of(2021, 9, 13), value); + } } diff --git a/src/test/resources/model/model.xcore b/src/test/resources/model/model.xcore index f1dcb58..7e44843 100755 --- a/src/test/resources/model/model.xcore +++ b/src/test/resources/model/model.xcore @@ -11,6 +11,8 @@ package org.eclipse.emfcloud.jackson.junit.model import org.eclipse.emf.ecore.EByteArray import org.eclipse.emf.ecore.EFeatureMapEntry import java.util.Map +import java.time.format.DateTimeFormatter +import java.time.LocalDate class User { id String userId @@ -62,6 +64,7 @@ class ETypes { contains StringMap[*] stringMapValues contains DataTypeMap[*] dataTypeMapValues unique URI[] uris + ELocalDate eLocalDate } class StringMap wraps Map.Entry { @@ -96,6 +99,13 @@ type URI wraps org.eclipse.emf.common.util.URI type UserType wraps String type ObjectType wraps Object type ObjectArrayType wraps Object[] +type ELocalDate wraps java.time.LocalDate + convert { + if (it === null) "" else DateTimeFormatter.ISO_LOCAL_DATE.format(it); + } + create { + if (it === null || it.trim().empty) null else LocalDate.parse(it, DateTimeFormatter.ISO_LOCAL_DATE); + } class PrimaryObject { String name