From 5965fddcd0877a4a07118844dd258d8a12f69dcd Mon Sep 17 00:00:00 2001 From: Carsten Wickner <11309681+CarstenWickner@users.noreply.github.com> Date: Sun, 4 Jun 2023 07:58:13 +0200 Subject: [PATCH] chore(example): show-case reflection based subtype resolution (#356) --- jsonschema-examples/pom.xml | 5 + .../examples/SubtypeLookUpExample.java | 122 ++++++++++++++++++ .../jsonschema/examples/ExampleTest.java | 3 +- .../examples/SubtypeLookUpExample-result.json | 37 ++++++ 4 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 jsonschema-examples/src/main/java/com/github/victools/jsonschema/examples/SubtypeLookUpExample.java create mode 100644 jsonschema-examples/src/test/resources/com/github/victools/jsonschema/examples/SubtypeLookUpExample-result.json diff --git a/jsonschema-examples/pom.xml b/jsonschema-examples/pom.xml index 66e509aa..6b118ddd 100644 --- a/jsonschema-examples/pom.xml +++ b/jsonschema-examples/pom.xml @@ -64,6 +64,11 @@ io.swagger.core.v3 swagger-annotations + + io.github.classgraph + classgraph + 4.8.149 + diff --git a/jsonschema-examples/src/main/java/com/github/victools/jsonschema/examples/SubtypeLookUpExample.java b/jsonschema-examples/src/main/java/com/github/victools/jsonschema/examples/SubtypeLookUpExample.java new file mode 100644 index 00000000..a2b9f255 --- /dev/null +++ b/jsonschema-examples/src/main/java/com/github/victools/jsonschema/examples/SubtypeLookUpExample.java @@ -0,0 +1,122 @@ +/* + * Copyright 2023 VicTools. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.github.victools.jsonschema.examples; + +import com.fasterxml.classmate.ResolvedType; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.github.victools.jsonschema.generator.Option; +import com.github.victools.jsonschema.generator.OptionPreset; +import com.github.victools.jsonschema.generator.SchemaGenerationContext; +import com.github.victools.jsonschema.generator.SchemaGenerator; +import com.github.victools.jsonschema.generator.SchemaGeneratorConfig; +import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder; +import com.github.victools.jsonschema.generator.SchemaVersion; +import com.github.victools.jsonschema.generator.SubtypeResolver; +import com.github.victools.jsonschema.generator.TypeContext; +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ClassInfo; +import io.github.classgraph.ClassInfoList; +import io.github.classgraph.ScanResult; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Example created to show-case generic subtype look-up. + *
+ * Using classgraph for determining type hierarchy. + */ +public class SubtypeLookUpExample implements SchemaGenerationExampleInterface { + + @Override + public ObjectNode generateSchema() { + SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2020_12, OptionPreset.PLAIN_JSON); + configBuilder.with(Option.DEFINITIONS_FOR_ALL_OBJECTS, Option.DEFINITIONS_FOR_MEMBER_SUPERTYPES); + configBuilder.forTypesInGeneral() + .withSubtypeResolver(new ClassGraphSubtypeResolver()); + SchemaGeneratorConfig config = configBuilder.build(); + SchemaGenerator generator = new SchemaGenerator(config); + return generator.generateSchema(Example.class); + } + + /** + * Simple implementation of a reflection based subtype resolver, considering only subtypes from a certain package. + */ + static class ClassGraphSubtypeResolver implements SubtypeResolver { + + private final ClassGraph classGraphConfig; + private ScanResult scanResult; + + ClassGraphSubtypeResolver() { + this.classGraphConfig = new ClassGraph() + .enableClassInfo() + .enableInterClassDependencies() + // in this example, only consider a certain set of potential subtypes + .acceptPackages("com.github.victools.jsonschema.examples"); + } + + private ScanResult getScanResult() { + if (this.scanResult == null) { + this.scanResult = this.classGraphConfig.scan(); + } + return this.scanResult; + } + + @Override + public void resetAfterSchemaGenerationFinished() { + if (this.scanResult != null) { + this.scanResult.close(); + this.scanResult = null; + } + } + + @Override + public List findSubtypes(ResolvedType declaredType, SchemaGenerationContext context) { + if (declaredType.getErasedType() == Object.class) { + return null; + } + ClassInfoList subtypes; + if (declaredType.isInterface()) { + subtypes = this.getScanResult().getClassesImplementing(declaredType.getErasedType()); + } else { + subtypes = this.getScanResult().getSubclasses(declaredType.getErasedType()); + } + if (!subtypes.isEmpty()) { + TypeContext typeContext = context.getTypeContext(); + return subtypes.loadClasses(true) + .stream() + .map(subclass -> typeContext.resolveSubtype(declaredType, subclass)) + .collect(Collectors.toList()); + } + return null; + } + } + + static class Example { + public BaseType declaredAsBaseType; + } + + interface BaseType { + } + + static class SubType1 implements BaseType { + public String text; + } + + static class SubType2 implements BaseType { + public List nested; + } +} diff --git a/jsonschema-examples/src/test/java/com/github/victools/jsonschema/examples/ExampleTest.java b/jsonschema-examples/src/test/java/com/github/victools/jsonschema/examples/ExampleTest.java index 1bd1dc42..2095bc07 100644 --- a/jsonschema-examples/src/test/java/com/github/victools/jsonschema/examples/ExampleTest.java +++ b/jsonschema-examples/src/test/java/com/github/victools/jsonschema/examples/ExampleTest.java @@ -37,7 +37,8 @@ public class ExampleTest { IfThenElseExample.class, JacksonDescriptionAsTitleExample.class, JacksonSubtypeDefinitionExample.class, - StrictTypeInfoExample.class + StrictTypeInfoExample.class, + SubtypeLookUpExample.class }) public void testExample(Class exampleType) throws Exception { SchemaGenerationExampleInterface exampleImplementation = exampleType.getDeclaredConstructor().newInstance(); diff --git a/jsonschema-examples/src/test/resources/com/github/victools/jsonschema/examples/SubtypeLookUpExample-result.json b/jsonschema-examples/src/test/resources/com/github/victools/jsonschema/examples/SubtypeLookUpExample-result.json new file mode 100644 index 00000000..80a5a1b8 --- /dev/null +++ b/jsonschema-examples/src/test/resources/com/github/victools/jsonschema/examples/SubtypeLookUpExample-result.json @@ -0,0 +1,37 @@ +{ + "$schema" : "https://json-schema.org/draft/2020-12/schema", + "$defs" : { + "BaseType" : { + "anyOf" : [ { + "$ref" : "#/$defs/SubType1" + }, { + "$ref" : "#/$defs/SubType2" + } ] + }, + "SubType1" : { + "type" : "object", + "properties" : { + "text" : { + "type" : "string" + } + } + }, + "SubType2" : { + "type" : "object", + "properties" : { + "nested" : { + "type" : "array", + "items" : { + "$ref" : "#/$defs/BaseType" + } + } + } + } + }, + "type" : "object", + "properties" : { + "declaredAsBaseType" : { + "$ref" : "#/$defs/BaseType" + } + } +}