Skip to content

Commit

Permalink
https://github.com/miho/VMF/issues/64
Browse files Browse the repository at this point in the history
  • Loading branch information
miho committed Aug 5, 2024
1 parent cc50842 commit 1c7c991
Show file tree
Hide file tree
Showing 12 changed files with 484 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class __VMF__${type.typeName}_Creator {

static ${VMF_RUNTIME_API_PKG}.${VMF_CORE_PKG_EXT}.Type getType() {
if(type ==null) {
type = ${VMF_RUNTIME_API_PKG}.${VMF_CORE_PKG_EXT}.Type.newInstance(true, false, "${type.packageName}.$type.typeName", ${type.packageName}.${type.typeName}.class);
type = ${VMF_RUNTIME_API_PKG}.${VMF_CORE_PKG_EXT}.Type.newInstance(true, false, false, "${type.packageName}.$type.typeName", ${type.packageName}.${type.typeName}.class);
}
return type;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ class __VMF__${type.typeName}_Creator {

static ${VMF_RUNTIME_API_PKG}.${VMF_CORE_PKG_EXT}.Type getType() {
if(type ==null) {
type = ${VMF_RUNTIME_API_PKG}.${VMF_CORE_PKG_EXT}.Type.newInstance(true, false, "${type.packageName}.$type.typeName", ${type.packageName}.${type.typeName}.class);
type = ${VMF_RUNTIME_API_PKG}.${VMF_CORE_PKG_EXT}.Type.newInstance(true, false, false, "${type.packageName}.$type.typeName", ${type.packageName}.${type.typeName}.class);
}
return type;
}
Expand Down Expand Up @@ -204,7 +204,7 @@ class __VMF__${type.typeName}_IFACE_ONLY_HELPER {

static ${VMF_RUNTIME_API_PKG}.${VMF_CORE_PKG_EXT}.Type getType() {
if(type ==null) {
type = ${VMF_RUNTIME_API_PKG}.${VMF_CORE_PKG_EXT}.Type.newInstance(true, false, "${type.packageName}.$type.typeName", ${type.packageName}.${type.typeName}.class);
type = ${VMF_RUNTIME_API_PKG}.${VMF_CORE_PKG_EXT}.Type.newInstance(true, false, true, "${type.packageName}.$type.typeName", ${type.packageName}.${type.typeName}.class);
}
return type;
}
Expand Down
121 changes: 88 additions & 33 deletions jackson/src/main/java/eu/mihosoft/vmf/jackson/VMFJacksonModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -367,52 +367,38 @@ private T deserializeNode(JsonParser p, DeserializationContext ctxt, JsonNode no
var arrayNode = (ArrayNode) element;
for (JsonNode e : arrayNode) {
// Deserialize the collection element
Object elementValue = ctxt.readValue(
Object elementValue = e.isValueNode()
// we reuse the scalar value handling from the deserializeField method
// it won't use the other parts of the method since we only use it for
// scalar values
? deserializeField(ctxt, e, elementTypeClass)
: ctxt.readValue(
e.traverse(ctxt.getParser().getCodec()),
getaVMFTypeClass(ctxt.getParser(), e, elementTypeClass));

// Add the element to the collection
collection.add(elementValue);
}
} else {
// Deserialize the collection element
Object elementValue = ctxt.readValue(
element.traverse(ctxt.getParser().getCodec()),
elementTypeClass);
Object elementValue = element.isValueNode()
// we reuse the scalar value handling from the deserializeField method
// it won't use the other parts of the method since we only use it for
// scalar values
? deserializeField(ctxt, element, elementTypeClass)
: ctxt.readValue(
element.traverse(ctxt.getParser().getCodec()),
getaVMFTypeClass(ctxt.getParser(), element, elementTypeClass));

// Add the element to the collection
collection.add(elementValue);
}
}
// Invoke the builder method
method.invoke(builder, collection);
} else {
// Handle null values
if (value.isNull()) {
method.invoke(builder, (Object) null);
} else if (value.isValueNode()) {
// Deserialize scalar value
Object paramValue;
if (paramType == String.class) {
paramValue = value.asText();
} else if (paramType == Integer.class||paramType == int.class) {
paramValue = value.asInt();
} else if (paramType == Long.class|| paramType == long.class) {
paramValue = value.asLong();
} else if (paramType == Double.class|| paramType == double.class) {
paramValue = value.asDouble();
} else if (paramType == Boolean.class|| paramType == boolean.class) {
paramValue = value.asBoolean();
} else {
// Deserialize non-scalar value
paramValue = ctxt.readValue(
value.traverse(ctxt.getParser().getCodec()), paramType);
}
method.invoke(builder, paramValue);
} else {
// Deserialize complex value
Object paramValue = ctxt.readValue(
value.traverse(ctxt.getParser().getCodec()), paramType);
method.invoke(builder, paramValue);
}
var paramValue = deserializeField(ctxt, value, paramType);
method.invoke(builder, paramValue);
}
} catch (Exception e) {
e.printStackTrace();
Expand All @@ -430,6 +416,39 @@ private T deserializeNode(JsonParser p, DeserializationContext ctxt, JsonNode no
}
}

private static Object deserializeField(DeserializationContext ctxt, JsonNode value, Class<?> paramType) throws IllegalAccessException, InvocationTargetException, IOException {
// Handle null values
if (value.isNull()) {
return null;
} else if (value.isValueNode()) {
// Deserialize scalar value
Object paramValue;
if (paramType == String.class) {
paramValue = value.asText();
} else if (paramType == Integer.class|| paramType == int.class) {
paramValue = value.asInt();
} else if (paramType == Long.class|| paramType == long.class) {
paramValue = value.asLong();
} else if (paramType == Double.class|| paramType == double.class) {
paramValue = value.asDouble();
} else if (paramType == Boolean.class|| paramType == boolean.class) {
paramValue = value.asBoolean();
} else if(paramType.isEnum()) {
paramValue = Enum.valueOf((Class<Enum>) paramType, value.asText());
} else {
// Deserialize non-scalar value
paramValue = ctxt.readValue(
value.traverse(ctxt.getParser().getCodec()), paramType);
}
return paramValue;
} else {
// Deserialize complex value
Object paramValue = ctxt.readValue(
value.traverse(ctxt.getParser().getCodec()), paramType);
return paramValue;
}
}

private Class<?> getaVMFTypeClass(JsonParser p, JsonNode node, Class<?> actualClass) {
String vmfTypeFieldName = "@vmf-type";

Expand Down Expand Up @@ -637,7 +656,7 @@ public void serialize(T value, JsonGenerator gen, SerializerProvider provider) t
}

/**
* Checks if the type is extended by another model type.
* Checks if the type is extended by another model type. Interface-only types are not considered.
* @param model the model object
* @param type the type to check
* @return {@code true} if the type is extended by another model type, {@code false} otherwise
Expand All @@ -648,6 +667,15 @@ private static boolean isTypeExtendedByModelType(VObject model, eu.mihosoft.vmf.

// now, check if type is a super type of any of the types
for (var t : allTypes) {

if(t == type) {
continue;
}

if (t.isInterfaceOnly()) {
continue;
}

if (t.superTypes().contains(type)) {
return true;
}
Expand All @@ -671,16 +699,43 @@ private static boolean isTypeExtendsModelTypeInProps(VObject model, eu.mihosoft.

// receive all property types
var allPropTypes = new HashSet<eu.mihosoft.vmf.runtime.core.Type>();

allTypes.forEach(t -> {

if(t.isInterfaceOnly()) {
return;
}

t.reflect().properties().forEach(p -> {
allPropTypes.add(p.getType().isListType()?
allTypesByName.get(p.getType().getElementTypeName().get())
: p.getType());
});
});

// check if any interface only type is not extended by any other type
for (var t : allPropTypes) {

if(t == null) {
continue;
}

if (t.isInterfaceOnly() && !isTypeExtendedByModelType(model, t)) {
throw new RuntimeException(
"Interface-only type '"
+ t.getName()
+ "' is not extended by any other type. Model cannot be de-/serialized since" +
" interface-only types cannot be instantiated.");
}
}

// now, check if type extends any of the types
for (var t : allPropTypes) {

if(t == null) {
continue;
}

if (type.superTypes().contains(t)) {
return true;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package eu.mihosoft.vmf.jackson.test.external_types01;

public enum ExternalEnum {
FIRST,
SECOND,
THIRD
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package eu.mihosoft.vmf.jackson.test.external_types01;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.mihosoft.vmf.jackson.VMFJacksonModule;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class ExternalTypeModel01Test {

@org.junit.jupiter.api.Test
void testModel() throws JsonProcessingException {

ExternalTypeModel01 model = ExternalTypeModel01.newBuilder()
.withName("Model 01")
.withEnumValue(ExternalEnum.SECOND)
.withTags(new String[]{"tag1", "tag2", "tag3"})
.withValues(new Integer[]{1, 2, 3})
.build();

var mapper = new ObjectMapper();

mapper.registerModule(VMFJacksonModule.newInstance(VMFJacksonModule.RUNTIME_TYPE.EXPERIMENTAL)
.withTypeAlias("external-type-model-01", ExternalTypeModel01.class.getName())
.withTypeAlias("external-enum", ExternalEnum.class.getName())
);

String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(model);
System.out.println(json);

// deserialize
ExternalTypeModel01 model2 = mapper.readValue(json, ExternalTypeModel01.class);

// serialize again
String json2 = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(model2);
System.out.println(json2);

// check if deserialization worked
assertTrue(model.vmf().content().equals(model2));

}

}
Loading

0 comments on commit 1c7c991

Please sign in to comment.