Skip to content

Commit

Permalink
json schema experiments
Browse files Browse the repository at this point in the history
  • Loading branch information
miho committed Aug 12, 2024
1 parent 22f36c1 commit 718b22a
Show file tree
Hide file tree
Showing 7 changed files with 387 additions and 66 deletions.
3 changes: 2 additions & 1 deletion config/common.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# version number (is used for core, runtime and default dependencies in plugin and plugin)
# publication.version = 0.3-SNAPSHOT
publication.version=0.2.9.3-SNAPSHOT
#publication.version=0.2.9.3-SNAPSHOT
publication.version=0.2.9.2
#publication.version=0.2.8.8
142 changes: 142 additions & 0 deletions jackson/src/main/java/eu/mihosoft/vmf/jackson/JsonSchemaGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package eu.mihosoft.vmf.jackson;

import eu.mihosoft.vmf.runtime.core.Property;
import eu.mihosoft.vmf.runtime.core.Type;
import eu.mihosoft.vmf.runtime.core.VObject;


import java.util.HashMap;
import java.util.Map;

public class JsonSchemaGenerator {

public static Map<String, Object> generateSchema(Class<? extends VObject> modelClass) {
Map<String, Object> schema = new HashMap<>();
schema.put("$schema", "http://json-schema.org/draft-07/schema#");
schema.put("title", modelClass.getSimpleName());
schema.put("type", "object");

Map<String, Object> properties = new HashMap<>();
schema.put("properties", properties);

Type type = VMFTypeUtils.forClass(modelClass);

for (Property property : type.reflect().properties()) {
properties.put(getFieldNameForProperty(property), getPropertySchema(property));
}

schema.put("definitions", generateDefinitions(type));

return schema;
}

private static Map<String, Object> getPropertySchema(Property property) {
Map<String, Object> propertySchema = new HashMap<>();

if (isValueType(property.getType())) {
propertySchema.put("type", mapValueType(property.getType()));
} else if (property.getType().isModelType()) {
// Complex object, reference definition
propertySchema.put("$ref", "#/definitions/" + property.getType().getName());
} else if (property.getType().isListType()) {
// Recognize VList types as arrays in JSON Schema
if (property.getType().getName().startsWith("eu.mihosoft.vcollections.VList")) {
propertySchema.put("type", "array");
Map<String, Object> itemsSchema = new HashMap<>();

// Handle polymorphic types with oneOf
Type elementType = VMFTypeUtils.forClass(property.getType().getElementTypeName().get());
if (elementType.isInterfaceOnly()) {
itemsSchema.put("oneOf", elementType.reflect().subTypes().stream().map(subType -> {
Map<String, Object> ref = new HashMap<>();
ref.put("$ref", "#/definitions/" + subType.getName());
return ref;
}).toArray());
} else {
itemsSchema.put("$ref", "#/definitions/" + property.getType().getElementTypeName().get());
}

propertySchema.put("items", itemsSchema);
}
} else if (VMFTypeUtils.isEnum(property.getType())) {
// Enum type
propertySchema.put("type", "string");
propertySchema.put("enum", VMFTypeUtils.getEnumConstants(property.getType()));
} else {
// Handle other types or fallback
propertySchema.put("type", "string");
}

return propertySchema;
}

private static Map<String, Object> generateDefinitions(Type type) {
Map<String, Object> definitions = new HashMap<>();
for (Type subType : type.reflect().allTypes()) {
if (subType.isInterfaceOnly()) continue;

Map<String, Object> definition = new HashMap<>();
definition.put("type", "object");

Map<String, Object> properties = new HashMap<>();
for (Property property : subType.reflect().properties()) {
properties.put(getFieldNameForProperty(property), getPropertySchema(property));
}

// Handle polymorphism using allOf to include super types if necessary
if (!subType.superTypes().isEmpty()) {
definition.put("allOf", subType.superTypes().stream().map(superType -> {
Map<String, Object> ref = new HashMap<>();
ref.put("$ref", "#/definitions/" + superType.getName());
return ref;
}).toArray());
}

definition.put("properties", properties);
definitions.put(subType.getName(), definition);
}
return definitions;
}

private static String mapValueType(Type type) {
if (isInteger(type)) return "integer";
if (isBoolean(type)) return "boolean";
if (isDouble(type) || isFloat(type)) return "number";
return "string";
}

public static boolean isInteger(Type type) {
return type.getName().equals("java.lang.Integer") || type.getName().equals("int") ||
type.getName().equals("java.lang.Short") || type.getName().equals("short") ||
type.getName().equals("java.lang.Long") || type.getName().equals("long");
}

public static boolean isBoolean(Type type) {
return type.getName().equals("java.lang.Boolean") || type.getName().equals("boolean");
}

public static boolean isFloat(Type type) {
return type.getName().equals("java.lang.Float") || type.getName().equals("float");
}

public static boolean isDouble(Type type) {
return type.getName().equals("java.lang.Double") || type.getName().equals("double");
}

public static boolean isValueType(Type type) {
String clsName = type.getName();

return clsName.equals("int") || clsName.equals("java.lang.Integer") ||
clsName.equals("short") || clsName.equals("java.lang.Short") ||
clsName.equals("long") || clsName.equals("java.lang.Long") ||
clsName.equals("boolean") || clsName.equals("java.lang.Boolean") ||
clsName.equals("float") || clsName.equals("java.lang.Float") ||
clsName.equals("double") || clsName.equals("java.lang.Double") ||
clsName.equals("java.lang.String");
}

private static String getFieldNameForProperty(Property p) {
var a = p.annotationByKey("vmf:jackson:rename");
return a.isPresent() ? a.get().getValue() : p.getName();
}
}
123 changes: 60 additions & 63 deletions jackson/src/main/java/eu/mihosoft/vmf/jackson/Main.java
Original file line number Diff line number Diff line change
@@ -1,65 +1,62 @@
package eu.mihosoft.vmf.jackson;

import java.util.ArrayList;
import java.util.List;

public class Main {

public static class MyClass {
private String name;
private int age;
private List<String> tags;
private MyClass child;

private final List<MyClass> children = new ArrayList<>();

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public List<String> getTags() {
return tags;
}

public void setTags(List<String> tags) {
this.tags = tags;
}

public MyClass getChild() {
return child;
}

public void setChild(MyClass child) {
this.child = child;
}

// children list
public List<MyClass> getChildren() {
return children;
}

public void setChildren(List<MyClass> children) {
this.children.clear();
this.children.addAll(children);
}




}

//package eu.mihosoft.vmf.jackson;
//
//import java.util.ArrayList;
//import java.util.List;
//
//
//public class Main {
//
// public static class MyClass {
// private String name;
// private int age;
// private List<String> tags;
// private MyClass child;
//
// private final List<MyClass> children = new ArrayList<>();
//
// public String getName() {
// return name;
// }
//
// public void setName(String name) {
// this.name = name;
// }
//
// public int getAge() {
// return age;
// }
//
// public void setAge(int age) {
// this.age = age;
// }
//
// public List<String> getTags() {
// return tags;
// }
//
// public void setTags(List<String> tags) {
// this.tags = tags;
// }
//
// public MyClass getChild() {
// return child;
// }
//
// public void setChild(MyClass child) {
// this.child = child;
// }
//
// // children list
// public List<MyClass> getChildren() {
// return children;
// }
//
// public void setChildren(List<MyClass> children) {
// this.children.clear();
// this.children.addAll(children);
// }
// }
//
// public static void main(String[] args) {
// // create a model tree
// MyClass model = new MyClass();
Expand Down Expand Up @@ -96,4 +93,4 @@ public void setChildren(List<MyClass> children) {
// e.printStackTrace();
// }
// }
}
//}
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ private static Class<?> getClassFromType(Type type) {
* @return the builder class object of the given class
* @throws ClassNotFoundException if the builder class cannot be found
*/
private Class<?> getBuilderClass(Class<?> clazz) throws ClassNotFoundException {
static Class<?> getBuilderClass(Class<?> clazz) throws ClassNotFoundException {
return Class.forName(clazz.getName() + "$Builder");
}
}
Expand Down
87 changes: 87 additions & 0 deletions jackson/src/main/java/eu/mihosoft/vmf/jackson/VMFTypeUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package eu.mihosoft.vmf.jackson;

import eu.mihosoft.vmf.runtime.core.Type;
import eu.mihosoft.vmf.runtime.core.VObject;

import java.lang.reflect.Method;

public final class VMFTypeUtils {
private VMFTypeUtils() {
throw new AssertionError("Don't instantiate me");
}

public static boolean isVMFType(Class<?> type) {
return eu.mihosoft.vmf.runtime.core.VObject.class.isAssignableFrom(type);
}

public static boolean isVMFType(Object obj) {
return isVMFType(obj.getClass());
}

/**
* Get the builder class for a given class.
* @param clazz the class object
* @return the builder class object of the given class
* @throws ClassNotFoundException if the builder class cannot be found
*/
public static Class<?> getBuilderClass(Class<?> clazz) throws ClassNotFoundException {
return Class.forName(clazz.getName() + "$Builder");
}

public static Type forClass(String clsName) {
try {
return forClass(Class.forName(clsName));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}

public static Type forClass(Class<?> cls) {
// check if VObject
if (!VObject.class.isAssignableFrom(cls)) {

}

try {
Class<?> builderClass = VMFTypeUtils.getBuilderClass(cls);
Object builder = builderClass.getDeclaredMethod("newInstance").invoke(null);

// Build the final object
Method buildMethod = builderClass.getDeclaredMethod("build");
var obj = (VObject) buildMethod.invoke(builder);

return obj.vmf().reflect().type();

} catch (Exception e) {
throw new RuntimeException(e);
}
}

public static boolean isEnum(Type type) {
// get type name
String typeName = type.getName();

// get class object from name
try {
Class<?> cls = Class.forName(typeName);
return cls.isEnum();
} catch (ClassNotFoundException ex) {
return false;
}
}

// get enum constants
public static Object[] getEnumConstants(Type type) {
// get type name
String typeName = type.getName();

// get class object from name
try {
Class<?> cls = Class.forName(typeName);
return cls.getEnumConstants();
} catch (ClassNotFoundException ex) {
return new Object[0];
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public void applyToConfigBuilder(SchemaGeneratorConfigBuilder builder) {

builder.withObjectMapper(objectMapper);

builder.forTypesInGeneral().with
// builder.forTypesInGeneral().with

// builder.with(new SchemaGeneratorTypeConfigPart() {
// @Override
Expand Down
Loading

0 comments on commit 718b22a

Please sign in to comment.