diff --git a/build.sbt b/build.sbt
deleted file mode 100755
index b55b892..0000000
--- a/build.sbt
+++ /dev/null
@@ -1,89 +0,0 @@
-import ReleaseTransformations._
-import sbtrelease.ReleasePlugin.autoImport.releaseStepCommand
-
-lazy val commonSettings = Seq(
- organization := "com.kjetland",
- organizationName := "mbknor",
- scalaVersion := "2.12.4",
- crossScalaVersions := Seq("2.11.12", "2.12.13", "2.13.4"),
- publishMavenStyle := true,
- publishArtifact in Test := false,
- pomIncludeRepository := { _ => false },
- publishTo := {
- val nexus = "https://oss.sonatype.org/"
- if (isSnapshot.value)
- Some("snapshots" at nexus + "content/repositories/snapshots")
- else
- Some("releases" at nexus + "service/local/staging/deploy/maven2")
- },
- credentials += Credentials(Path.userHome / ".ivy2" / ".credentials_sonatype"),
- homepage := Some(url("https://github.com/mbknor/mbknor-jackson-jsonSchema")),
- licenses := Seq("MIT" -> url("https://github.com/mbknor/mbknor-jackson-jsonSchema/blob/master/LICENSE.txt")),
- startYear := Some(2016),
- pomExtra := (
-
- git@github.com:mbknor/mbknor-jackson-jsonSchema.git
- scm:git:git@github.com:mbknor/mbknor-jackson-jsonSchema.git
-
-
-
- mbknor
- Morten Kjetland
- https://github.com/mbknor
-
- ),
- compileOrder in Test := CompileOrder.Mixed,
- javacOptions ++= Seq("-source", "1.8", "-target", "1.8"),
- scalacOptions ++= Seq("-unchecked", "-deprecation"),
- releaseCrossBuild := true,
- releasePublishArtifactsAction := PgpKeys.publishSigned.value,
- scalacOptions in(Compile, doc) ++= Seq(scalaVersion.value).flatMap {
- case v if v.startsWith("2.12") =>
- Seq("-no-java-comments") //workaround for scala/scala-dev#249
- case _ =>
- Seq()
- },
- packageOptions in (Compile, packageBin) +=
- Package.ManifestAttributes( "Automatic-Module-Name" -> "mbknor.jackson.jsonschema" )
-)
-
-
-val jacksonVersion = "2.12.1"
-val jacksonModuleScalaVersion = "2.12.1"
-val slf4jVersion = "1.7.26"
-
-lazy val deps = Seq(
- "com.fasterxml.jackson.core" % "jackson-databind" % jacksonVersion,
- "javax.validation" % "validation-api" % "2.0.1.Final",
- "org.slf4j" % "slf4j-api" % slf4jVersion,
- "io.github.classgraph" % "classgraph" % "4.8.21",
- "org.scalatest" %% "scalatest" % "3.0.8" % "test",
- "ch.qos.logback" % "logback-classic" % "1.2.3" % "test",
- "com.github.java-json-tools" % "json-schema-validator" % "2.2.11" % "test",
- "com.fasterxml.jackson.module" %% "jackson-module-scala" % jacksonModuleScalaVersion % "test",
- "com.fasterxml.jackson.module" % "jackson-module-kotlin" % jacksonVersion % "test",
- "com.fasterxml.jackson.datatype" % "jackson-datatype-jdk8" % jacksonVersion % "test",
- "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % jacksonVersion % "test",
- "joda-time" % "joda-time" % "2.10.1" % "test",
- "com.fasterxml.jackson.datatype" % "jackson-datatype-joda" % jacksonVersion % "test"
-)
-
-lazy val root = (project in file("."))
- .settings(name := "mbknor-jackson-jsonSchema")
- .settings(commonSettings: _*)
- .settings(libraryDependencies ++= (deps))
-
-
-releaseProcess := Seq[ReleaseStep](
- checkSnapshotDependencies,
- inquireVersions,
- runTest,
- setReleaseVersion,
- commitReleaseVersion,
- tagRelease,
- publishArtifacts,
- setNextVersion,
- commitNextVersion,
- pushChanges,
- releaseStepCommand("sonatypeRelease")
-)
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..5587b3e
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,168 @@
+
+
+ 4.0.0
+ net.almson
+ mbknor-jackson-jsonschema-java
+ 1.0.39
+ jar
+
+ mbknor-jackson-jsonSchema
+ https://github.com/mbknor/mbknor-jackson-jsonSchema
+
+
+ MIT
+ https://github.com/mbknor/mbknor-jackson-jsonSchema/blob/master/LICENSE.txt
+ repo
+
+
+ mbknor-jackson-jsonSchema
+ 2016
+
+ mbknor
+ https://github.com/mbknor/mbknor-jackson-jsonSchema
+
+
+ git@github.com:almson/mbknor-jackson-jsonSchema-java.git
+ scm:git:git@github.com:almson/mbknor-jackson-jsonSchema-java.git
+
+
+
+ mbknor
+ Morten Kjetland
+ https://github.com/mbknor
+
+
+ almson
+ Aleksandr Dubinsky
+ https://github.com/almson
+
+
+
+
+ UTF-8
+ 2.13.0
+
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.22
+ provided
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson-version}
+
+
+ javax.validation
+ validation-api
+ 2.0.1.Final
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.32
+
+
+ io.github.classgraph
+ classgraph
+ 4.8.138
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.8.1
+ test
+
+
+ org.slf4j
+ slf4j-simple
+ 1.7.32
+ test
+
+
+ com.github.java-json-tools
+ json-schema-validator
+ 2.2.11
+ test
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jdk8
+ ${jackson-version}
+ test
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+ ${jackson-version}
+ test
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-joda
+ ${jackson-version}
+ test
+
+
+ joda-time
+ joda-time
+ 2.10.1
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+ 17
+ 17
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.3.1
+
+
+ attach-javadoc
+
+ jar
+
+
+ none
+ false
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 3.2.1
+
+
+ attach-sources
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.22.2
+
+ false
+
+
+
+
+
diff --git a/project/build.properties b/project/build.properties
deleted file mode 100644
index 6adcdc7..0000000
--- a/project/build.properties
+++ /dev/null
@@ -1 +0,0 @@
-sbt.version=1.3.3
diff --git a/project/plugins.sbt b/project/plugins.sbt
deleted file mode 100644
index 2b7d7db..0000000
--- a/project/plugins.sbt
+++ /dev/null
@@ -1,7 +0,0 @@
-addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.3")
-
-addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2")
-
-addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.9")
-
-addSbtPlugin("com.hanhuy.sbt" % "kotlin-plugin" % "2.0.0")
diff --git a/release-howto.md b/release-howto.md
deleted file mode 100644
index 7ce0356..0000000
--- a/release-howto.md
+++ /dev/null
@@ -1,3 +0,0 @@
-Run
-
- SBT_OPTS="-Xms512M -Xmx1024M -Xss2M -XX:MaxMetaspaceSize=1024M" sbt release
diff --git a/src/main/java/com/kjetland/jackson/jsonSchema/AbstractJsonFormatVisitorWithSerializerProvider.java b/src/main/java/com/kjetland/jackson/jsonSchema/AbstractJsonFormatVisitorWithSerializerProvider.java
new file mode 100644
index 0000000..2a1514f
--- /dev/null
+++ b/src/main/java/com/kjetland/jackson/jsonSchema/AbstractJsonFormatVisitorWithSerializerProvider.java
@@ -0,0 +1,19 @@
+package com.kjetland.jackson.jsonSchema;
+
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWithSerializerProvider;
+
+abstract class AbstractJsonFormatVisitorWithSerializerProvider implements JsonFormatVisitorWithSerializerProvider {
+
+ SerializerProvider provider;
+
+ @Override
+ public SerializerProvider getProvider() {
+ return provider;
+ }
+
+ @Override
+ public void setProvider(SerializerProvider provider) {
+ this.provider = provider;
+ }
+}
diff --git a/src/main/java/com/kjetland/jackson/jsonSchema/DefinitionsHandler.java b/src/main/java/com/kjetland/jackson/jsonSchema/DefinitionsHandler.java
new file mode 100644
index 0000000..7ae98ae
--- /dev/null
+++ b/src/main/java/com/kjetland/jackson/jsonSchema/DefinitionsHandler.java
@@ -0,0 +1,114 @@
+package com.kjetland.jackson.jsonSchema;
+
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import lombok.RequiredArgsConstructor;
+
+// Class that manages creating new definitions or getting $refs to existing definitions
+@RequiredArgsConstructor
+class DefinitionsHandler {
+
+ record DefinitionInfo(String ref, JsonObjectFormatVisitor jsonObjectFormatVisitor) {}
+ record WorkInProgress(JavaType typeInProgress, ObjectNode nodeInProgress) {}
+
+ final JsonSchemaConfig config;
+
+ final private Map class2Ref = new HashMap<>();
+ final private ObjectNode definitionsNode = JsonNodeFactory.instance.objectNode();
+
+ // Used to combine multiple invocations of `getOrCreateDefinition` when processing polymorphism.
+ private Deque> workInProgressStack = new LinkedList<>();
+ private Optional workInProgress = Optional.empty();
+
+
+ @FunctionalInterface
+ interface VisitorSupplier {
+
+ JsonObjectFormatVisitor get(JavaType type, ObjectNode t) throws JsonMappingException;
+ }
+
+ public void pushWorkInProgress() {
+ workInProgressStack.push(workInProgress);
+ workInProgress = Optional.empty();
+ }
+
+ public void popworkInProgress() {
+ workInProgress = workInProgressStack.pop();
+ }
+
+ // Either creates new definitions or return $ref to existing one
+ public DefinitionInfo getOrCreateDefinition(JavaType type, VisitorSupplier visitorSupplier) throws JsonMappingException {
+
+ var ref = class2Ref.get(type);
+ if (ref != null)
+ // Return existing definition
+ if (workInProgress.isEmpty())
+ return new DefinitionInfo(ref, null);
+ else {
+ // this is a recursive polymorphism call
+ if (type != workInProgress.get().typeInProgress)
+ throw new IllegalStateException("Wrong type - working on " + workInProgress.get().typeInProgress + " - got " + type);
+
+ var visitor = visitorSupplier.get(type, workInProgress.get().nodeInProgress);
+ return new DefinitionInfo(null, visitor);
+ }
+
+ // Build new definition
+ var retryCount = 0;
+ var definitionName = getDefinitionName(type);
+ var shortRef = definitionName;
+ var longRef = "#/definitions/" + definitionName;
+ while (class2Ref.containsValue(longRef)) {
+ retryCount = retryCount + 1;
+ shortRef = definitionName + "_" + retryCount;
+ longRef = "#/definitions/" + definitionName + "_" + retryCount;
+ }
+ class2Ref.put(type, longRef);
+
+ var node = JsonNodeFactory.instance.objectNode();
+
+ // When processing polymorphism, we might get multiple recursive calls to getOrCreateDefinition - this is a way to combine them
+ workInProgress = Optional.of(new WorkInProgress(type, node));
+ definitionsNode.set(shortRef, node);
+
+ var visitor = visitorSupplier.get(type, node);
+
+ workInProgress = Optional.empty();
+
+ return new DefinitionInfo(longRef, visitor);
+ }
+
+ public ObjectNode getFinalDefinitionsNode() {
+ if (class2Ref.isEmpty())
+ return null;
+ else
+ return definitionsNode;
+ }
+
+ private String getDefinitionName(JavaType type) {
+ var baseName = config.useTypeIdForDefinitionName
+ ? type.getRawClass().getTypeName()
+ : Utils.extractTypeName(type);
+
+ if (type.hasGenericTypes()) {
+ var containedTypeNames
+ = IntStream.range(0, type.containedTypeCount())
+ .mapToObj(type::containedType)
+ .map(this::getDefinitionName)
+ .collect(Collectors.joining(","));
+ return baseName + "(" + containedTypeNames + ")";
+ } else {
+ return baseName;
+ }
+ }
+}
diff --git a/src/main/java/com/kjetland/jackson/jsonSchema/JsonSchemaConfig.java b/src/main/java/com/kjetland/jackson/jsonSchema/JsonSchemaConfig.java
new file mode 100644
index 0000000..09233a2
--- /dev/null
+++ b/src/main/java/com/kjetland/jackson/jsonSchema/JsonSchemaConfig.java
@@ -0,0 +1,87 @@
+package com.kjetland.jackson.jsonSchema;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Builder.Default;
+import lombok.experimental.FieldDefaults;
+
+@Builder(toBuilder = true)
+@FieldDefaults(level = AccessLevel.PUBLIC, makeFinal = true)
+public final class JsonSchemaConfig {
+
+ public static final Map DEFAULT_DATE_FORMAT_MAPPING
+ = new HashMap() {{
+ // Java7 dates
+ put("java.time.LocalDateTime", "datetime-local");
+ put("java.time.OffsetDateTime", "datetime");
+ put("java.time.LocalDate", "date");
+
+ // Joda-dates
+ put("org.joda.time.LocalDate", "date");
+ }};
+
+ public static final JsonSchemaConfig VANILLA = JsonSchemaConfig.builder().build();
+
+ /**
+ * Use this configuration if using the JsonSchema to generate HTML5 GUI, eg. by using https://github.com/jdorn/json-editor
+ *
+ * The following options are enabled:
+ * {@code autoGenerateTitleForProperties} - If property is named "someName", we will add {"title": "Some Name"}
+ * {@code defaultArrayFormat} - this will result in a better gui than te default one.
+ */
+ public static final JsonSchemaConfig JSON_EDITOR
+ = JsonSchemaConfig.builder()
+ .autoGenerateTitleForProperties(true)
+ .defaultArrayFormat("table")
+ .useOneOfForOption(true)
+ .usePropertyOrdering(true)
+ .hidePolymorphismTypeProperty(true)
+ .useMinLengthForNotNull(true)
+ .customType2FormatMapping(DEFAULT_DATE_FORMAT_MAPPING)
+ .useMultipleEditorSelectViaProperty(true)
+ .uniqueItemClasses(new HashSet>() {{
+ add(java.util.Set.class);
+ }})
+ .build();
+
+ /**
+ * This configuration is exactly like the vanilla JSON schema generator, except that "nullables" have been turned on:
+ * `useOneOfForOption` and `useOneForNullables` have both been set to `true`. With this configuration you can either
+ * use `Optional` or `Option`, or a standard nullable Java type and get back a schema that allows nulls.
+ *
+ *
+ * If you need to mix nullable and non-nullable types, you may override the nullability of the type by either setting
+ * a `NotNull` annotation on the given property, or setting the `required` attribute of the `JsonProperty` annotation.
+ */
+ public static final JsonSchemaConfig NULLABLE
+ = JsonSchemaConfig.builder()
+ .useOneOfForOption(true)
+ .useOneOfForNullables(true)
+ .build();
+
+ @Default boolean autoGenerateTitleForProperties = false;
+ @Default String defaultArrayFormat = null;
+ @Default boolean useOneOfForOption = false;
+ @Default boolean useOneOfForNullables = false;
+ @Default boolean usePropertyOrdering = false;
+ @Default boolean hidePolymorphismTypeProperty = false;
+ @Default boolean useMinLengthForNotNull = false;
+ @Default boolean useTypeIdForDefinitionName = false;
+ @Default Map customType2FormatMapping = new HashMap<>();
+ @Default boolean useMultipleEditorSelectViaProperty = false; // https://github.com/jdorn/json-editor/issues/709
+ @Default Set> uniqueItemClasses = new HashSet<>(); // If rendering array and type is instanceOf class in this set, then we add 'uniqueItems": true' to schema - See // https://github.com/jdorn/json-editor for more info
+ @Default Map, Class>> classTypeReMapping = new HashMap<>(); // Can be used to prevent rendering using polymorphism for specific classes.
+ @Default Map> jsonSuppliers = new HashMap<>(); // Suppliers in this map can be accessed using @JsonSchemaInject(jsonSupplierViaLookup = "lookupKey")
+ @Default SubclassesResolver subclassesResolver = new SubclassesResolver();
+ @Default boolean failOnUnknownProperties = true;
+ @Default List> javaxValidationGroups = new ArrayList<>(); // Used to match against different validation-groups (javax.validation.constraints)
+ @Default JsonSchemaDraft jsonSchemaDraft = JsonSchemaDraft.DRAFT_04;
+}
diff --git a/src/main/scala/com/kjetland/jackson/jsonSchema/JsonSchemaDraft.java b/src/main/java/com/kjetland/jackson/jsonSchema/JsonSchemaDraft.java
similarity index 100%
rename from src/main/scala/com/kjetland/jackson/jsonSchema/JsonSchemaDraft.java
rename to src/main/java/com/kjetland/jackson/jsonSchema/JsonSchemaDraft.java
diff --git a/src/main/java/com/kjetland/jackson/jsonSchema/JsonSchemaGenerator.java b/src/main/java/com/kjetland/jackson/jsonSchema/JsonSchemaGenerator.java
new file mode 100755
index 0000000..7ef09f3
--- /dev/null
+++ b/src/main/java/com/kjetland/jackson/jsonSchema/JsonSchemaGenerator.java
@@ -0,0 +1,234 @@
+package com.kjetland.jackson.jsonSchema;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
+import com.fasterxml.jackson.databind.introspect.AnnotatedClassResolver;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+import com.fasterxml.jackson.databind.node.*;
+import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaFormat;
+import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaInject;
+import java.lang.annotation.Annotation;
+import java.util.List;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import javax.validation.groups.Default;
+import lombok.extern.slf4j.Slf4j;
+
+
+@Slf4j
+public class JsonSchemaGenerator {
+
+ final ObjectMapper objectMapper;
+ final JsonSchemaConfig config;
+
+ /**
+ * JSON Schema Generator.
+ * @param rootObjectMapper pre-configured ObjectMapper
+ */
+ public JsonSchemaGenerator(ObjectMapper rootObjectMapper) {
+ this(rootObjectMapper, JsonSchemaConfig.VANILLA);
+ }
+
+ /**
+ * JSON Schema Generator.
+ * @param rootObjectMapper pre-configured ObjectMapper
+ * @param config by default, {@link JsonSchemaConfig#VANILLA}.
+ * Use {@link JsonSchemaConfig#JSON_EDITOR} for {@link https://github.com/jdorn/json-editor JSON GUI}.
+ */
+ public JsonSchemaGenerator(ObjectMapper rootObjectMapper, JsonSchemaConfig config) {
+ this.objectMapper = rootObjectMapper;
+ this.config = config;
+ }
+
+ public JsonNode generateJsonSchema(Class> clazz) throws JsonMappingException {
+ return generateJsonSchema(clazz, null, null);
+ }
+
+ public JsonNode generateJsonSchema(JavaType javaType) throws JsonMappingException {
+ return generateJsonSchema(javaType, null, null);
+ }
+
+ public JsonNode generateJsonSchema(Class> clazz, String title, String description) throws JsonMappingException {
+
+ var clazzToUse = tryToReMapType(clazz);
+
+ var javaType = objectMapper.constructType(clazzToUse);
+
+ return generateJsonSchema(javaType, title, description);
+ }
+
+ public JsonNode generateJsonSchema(JavaType javaType, String title, String description) throws JsonMappingException {
+
+ var rootNode = JsonNodeFactory.instance.objectNode();
+
+ rootNode.put("$schema", config.jsonSchemaDraft.url);
+
+ if (title == null)
+ title = Utils.camelCaseToSentenceCase(javaType.getRawClass().getSimpleName());
+ if (!title.isEmpty())
+ // If root class is annotated with @JsonSchemaTitle, it will later override this title
+ rootNode.put("title", title);
+
+ if (description != null)
+ // If root class is annotated with @JsonSchemaDescription, it will later override this description
+ rootNode.put("description", description);
+
+
+ var definitionsHandler = new DefinitionsHandler(config);
+ var rootVisitor = new JsonSchemaGeneratorVisitor(this, 0, rootNode, definitionsHandler, null);
+
+
+ objectMapper.acceptJsonFormatVisitor(javaType, rootVisitor);
+
+ var definitionsNode = definitionsHandler.getFinalDefinitionsNode();
+ if (definitionsNode != null)
+ rootNode.set("definitions", definitionsNode);
+
+ return rootNode;
+ }
+
+
+ JavaType tryToReMapType(JavaType originalType) {
+ Class> mappedToClass = config.classTypeReMapping.get(originalType.getRawClass());
+ if (mappedToClass != null) {
+ log.trace("Class {} is remapped to {}", originalType, mappedToClass);
+ return objectMapper.getTypeFactory().constructType(mappedToClass);
+ }
+ else
+ return originalType;
+ }
+
+ Class> tryToReMapType(Class> originalClass) {
+ Class> mappedToClass = config.classTypeReMapping.get(originalClass);
+ if (mappedToClass != null) {
+ log.trace("Class {} is remapped to {}", originalClass, mappedToClass);
+ return mappedToClass;
+ }
+ else
+ return originalClass;
+ }
+
+ String resolvePropertyFormat(JavaType type) {
+ var omConfig = objectMapper.getDeserializationConfig();
+ var annotatedClass = AnnotatedClassResolver.resolve(omConfig, type, omConfig);
+ var annotation = annotatedClass.getAnnotation(JsonSchemaFormat.class);
+ if (annotation != null)
+ return annotation.value();
+
+ var rawClassName = type.getRawClass().getName();
+ return config.customType2FormatMapping.get(rawClassName);
+ }
+
+ String resolvePropertyFormat(BeanProperty prop) {
+ var annotation = prop.getAnnotation(JsonSchemaFormat.class);
+ if (annotation != null)
+ return annotation.value();
+
+ var rawClassName = prop.getType().getRawClass().getName();
+ return config.customType2FormatMapping.get(rawClassName);
+ }
+
+ /** Tries to retrieve an annotation and validates that it is applicable. */
+ T selectAnnotation(BeanProperty prop, Class annotationClass) {
+ if (prop == null)
+ return null;
+ var ann = prop.getAnnotation(annotationClass);
+ if (ann == null || !annotationIsApplicable(ann))
+ return null;
+ return ann;
+ }
+
+ T selectAnnotation(AnnotatedClass annotatedClass, Class annotationClass) {
+ var ann = annotatedClass.getAnnotation(annotationClass);
+ if (ann == null || !annotationIsApplicable(ann))
+ return null;
+ return ann;
+ }
+
+ // Checks to see if a javax.validation field that makes our field required is present.
+ boolean validationAnnotationRequired(BeanProperty prop) {
+ return selectAnnotation(prop, NotNull.class) != null
+ || selectAnnotation(prop, NotBlank.class) != null
+ || selectAnnotation(prop, NotEmpty.class) != null;
+ }
+
+ /** Verifies that the annotation is applicable based on the config.javaxValidationGroups. */
+ boolean annotationIsApplicable(Annotation annotation) {
+ var desiredGroups = config.javaxValidationGroups;
+ if (desiredGroups == null || desiredGroups.isEmpty())
+ desiredGroups = List.of (Default.class);
+
+ var annotationGroups = Utils.extractGroupsFromAnnotation(annotation);
+ if (annotationGroups.isEmpty())
+ annotationGroups = List.of (Default.class);
+
+ for (var group : annotationGroups)
+ if (desiredGroups.contains (group))
+ return true;
+ return false;
+ }
+
+ TypeSerializer getTypeSerializer(JavaType baseType) throws JsonMappingException {
+
+ return objectMapper
+ .getSerializerFactory()
+ .createTypeSerializer(objectMapper.getSerializationConfig(), baseType);
+ }
+
+
+ /**
+ * @returns the value of merge
+ */
+ boolean injectFromAnnotation(ObjectNode node, JsonSchemaInject injectAnnotation) throws JsonMappingException {
+ // Must parse json
+ JsonNode injectedNode;
+ try {
+ injectedNode = objectMapper.readTree(injectAnnotation.json());
+ }
+ catch(JsonProcessingException e) {
+ throw new JsonMappingException("Could not parse JsonSchemaInject.json", e);
+ }
+
+ // Apply the JSON supplier (may be a no-op)
+ try {
+ var jsonSupplier = injectAnnotation.jsonSupplier().newInstance();
+ var jsonNode = jsonSupplier.get();
+ if (jsonNode != null)
+ Utils.merge (injectedNode, jsonNode);
+ }
+ catch (InstantiationException|IllegalAccessException e) {
+ throw new JsonMappingException("Could not call JsonSchemaInject.jsonSupplier constructor", e);
+ }
+
+ // Apply the JSON-supplier-via-lookup
+ if (!injectAnnotation.jsonSupplierViaLookup().isEmpty()) {
+ var jsonSupplier = config.jsonSuppliers.get(injectAnnotation.jsonSupplierViaLookup());
+ if (jsonSupplier == null)
+ throw new JsonMappingException("@JsonSchemaInject(jsonSupplierLookup='"+injectAnnotation.jsonSupplierViaLookup()+"') does not exist in ctx.config.jsonSupplierLookup-map");
+ var jsonNode = jsonSupplier.get();
+ if (jsonNode != null)
+ Utils.merge(injectedNode, jsonNode);
+ }
+
+ //
+ for (var v : injectAnnotation.strings())
+ Utils.visit(injectedNode, v.path(), (o, n) -> o.put(n, v.value()));
+ for (var v : injectAnnotation.ints())
+ Utils.visit(injectedNode, v.path(), (o, n) -> o.put(n, v.value()));
+ for (var v : injectAnnotation.bools())
+ Utils.visit(injectedNode, v.path(), (o, n) -> o.put(n, v.value()));
+
+ var injectOverridesAll = injectAnnotation.overridesAll();
+ if (injectOverridesAll) {
+ // Since we're not merging, we must remove all content of thisObjectNode before injecting.
+ // We cannot just "replace" it with injectJsonNode, since thisObjectNode already have been added to its parent
+ node.removeAll();
+ }
+
+ Utils.merge(node, injectedNode);
+
+ return injectOverridesAll;
+ }
+}
diff --git a/src/main/java/com/kjetland/jackson/jsonSchema/JsonSchemaGeneratorVisitor.java b/src/main/java/com/kjetland/jackson/jsonSchema/JsonSchemaGeneratorVisitor.java
new file mode 100644
index 0000000..6e42e2c
--- /dev/null
+++ b/src/main/java/com/kjetland/jackson/jsonSchema/JsonSchemaGeneratorVisitor.java
@@ -0,0 +1,788 @@
+package com.kjetland.jackson.jsonSchema;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyDescription;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.introspect.AnnotatedClassResolver;
+import com.fasterxml.jackson.databind.jsonFormatVisitors.*;
+import com.fasterxml.jackson.databind.jsontype.impl.MinimalClassNameIdResolver;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.kjetland.jackson.jsonSchema.DefinitionsHandler.DefinitionInfo;
+import com.kjetland.jackson.jsonSchema.annotations.*;
+import java.lang.annotation.Annotation;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.validation.constraints.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@RequiredArgsConstructor
+class JsonSchemaGeneratorVisitor extends AbstractJsonFormatVisitorWithSerializerProvider implements JsonFormatVisitorWrapper {
+
+ final JsonSchemaGenerator ctx;
+
+ final int level; // = 0
+ final ObjectNode node; // = JsonNodeFactory.instance.objectNode()
+ final DefinitionsHandler definitionsHandler;
+ final BeanProperty currentProperty; // This property may represent the BeanProperty when we're directly processing beneath the property
+
+ /** Tries to retrieve an annotation and validates that it is applicable. */
+ private T tryGetAnnotation(Class annotationClass) {
+ return ctx.selectAnnotation(currentProperty, annotationClass);
+ }
+
+ JsonSchemaGeneratorVisitor createChildVisitor(ObjectNode childNode, BeanProperty currentProperty) {
+ return new JsonSchemaGeneratorVisitor(ctx, level + 1, childNode, definitionsHandler, currentProperty);
+ }
+
+ String extractDefaultValue() {
+ // Scala way (ugly and confusing)
+// return selectAnnotation(p, JsonProperty.class)
+// .map(JsonProperty::defaultValue)
+// .or (() ->
+// selectAnnotation(p, JsonSchemaDefault.class)
+// .map (JsonSchemaDefault::value));
+
+ // Plain java
+ var jp = tryGetAnnotation(JsonProperty.class);
+ if (jp != null)
+ return jp.defaultValue();
+
+ var jsd = tryGetAnnotation(JsonSchemaDefault.class);
+ if (jsd != null)
+ return jsd.value();
+
+ return null;
+
+ // Hypothetical null operators
+// return selectAnnotation(p, JsonProperty.class)?.defaultValue()
+// ?? selectAnnotation(p, JsonSchemaDefault.class)?.value();
+ }
+
+// @RequiredArgsConstructor
+ class MyJsonValueFormatVisitor
+ extends AbstractJsonFormatVisitorWithSerializerProvider
+ implements
+ JsonStringFormatVisitor,
+ JsonNumberFormatVisitor,
+ JsonIntegerFormatVisitor,
+ JsonBooleanFormatVisitor {
+
+ @Override
+ public void format(JsonValueFormat format) {
+ node.put("format", format.toString());
+ }
+
+ @Override
+ public void enumTypes(Set enums) {
+ log.trace("JsonStringFormatVisitor-enum.enumTypes: {}", enums);
+
+ var enumValuesNode = JsonNodeFactory.instance.arrayNode();
+ for (var e : enums)
+ enumValuesNode.add(e);
+ node.set("enum", enumValuesNode);
+ }
+
+ @Override public void numberType(JsonParser.NumberType type) {
+ log.trace("JsonNumberFormatVisitor.numberType: {}", type);
+ }
+ }
+
+ @Override
+ public JsonStringFormatVisitor expectStringFormat(JavaType type) {
+ log.trace("expectStringFormat {}", type);
+
+ node.put("type", "string");
+
+ var notBlankAnnotation = tryGetAnnotation(NotBlank.class);
+ if (notBlankAnnotation != null)
+ node.put("pattern", "^.*\\S+.*$");
+
+ var patternAnnotation = tryGetAnnotation(Pattern.class);
+ if (patternAnnotation != null)
+ node.put("pattern", patternAnnotation.regexp());
+
+ var patternListAnnotation = tryGetAnnotation(Pattern.List.class);
+ if (patternListAnnotation != null) {
+ String pattern = "^";
+ for (var p : patternListAnnotation.value())
+ pattern += "(?=" + p.regexp() + ")";
+ pattern += ".*$";
+ node.put("pattern", pattern);
+ }
+
+ var defaultValue = extractDefaultValue();
+ if (defaultValue != null)
+ node.put("default", defaultValue);
+
+ // Look for @JsonSchemaExamples
+ var examplesAnnotation = tryGetAnnotation(JsonSchemaExamples.class);
+ if (examplesAnnotation != null) {
+ var examples = JsonNodeFactory.instance.arrayNode();
+ for (var example : examplesAnnotation.value())
+ examples.add(example);
+ node.set("examples", examples);
+ }
+
+ // Look for @Email
+ var emailAnnotation = tryGetAnnotation(Email.class);
+ if (emailAnnotation != null)
+ node.put("format", "email");
+
+ // Look for a @Size annotation, which should have a set of min/max properties.
+ var minAndMaxLengthAnnotation = tryGetAnnotation(Size.class);
+ var notNullAnnotation = tryGetAnnotation(NotNull.class);
+ var notEmptyAnnotation = tryGetAnnotation(NotEmpty.class);
+ if (minAndMaxLengthAnnotation != null) {
+ if (minAndMaxLengthAnnotation.min() != 0)
+ node.put("minLength", minAndMaxLengthAnnotation.min());
+ if (minAndMaxLengthAnnotation.max() != Integer.MAX_VALUE)
+ node.put("maxLength", minAndMaxLengthAnnotation.max());
+ }
+ else if (ctx.config.useMinLengthForNotNull && notNullAnnotation != null)
+ node.put("minLength", 1);
+ else if (notEmptyAnnotation != null || notBlankAnnotation != null)
+ node.put("minLength", 1);
+
+ return new MyJsonValueFormatVisitor();
+ }
+
+ @Override
+ public JsonArrayFormatVisitor expectArrayFormat(JavaType type) {
+ log.trace("expectArrayFormat {}", type);
+
+ node.put("type", "array");
+
+ if (ctx.config.uniqueItemClasses.stream().anyMatch(c -> type.getRawClass().isAssignableFrom(c))) {
+ // Adding '"uniqueItems": true' to be used with https://github.com/jdorn/json-editor
+ node.put("uniqueItems", true);
+ node.put("format", "checkbox");
+ } else {
+ // Try to set default format
+ if (ctx.config.defaultArrayFormat != null)
+ node.put("format", ctx.config.defaultArrayFormat);
+ }
+
+ var sizeAnnotation = tryGetAnnotation(Size.class);
+ if (sizeAnnotation != null) {
+ node.put("minItems", sizeAnnotation.min());
+ node.put("maxItems", sizeAnnotation.max());
+ }
+
+ var notEmptyAnnotation = tryGetAnnotation(NotEmpty.class);
+ if (notEmptyAnnotation != null)
+ node.put("minItems", 1);
+
+ var defaultValue = extractDefaultValue();
+ if (defaultValue != null)
+ node.put("default", defaultValue);
+
+
+ var itemsNode = JsonNodeFactory.instance.objectNode();
+ node.set("items", itemsNode);
+
+ // We get improved result while processing scala-collections by getting elementType this way
+ // instead of using the one which we receive in JsonArrayFormatVisitor.itemsFormat
+ // This approach also works for Java
+ JavaType preferredElementType = type.getContentType();
+
+ class MyVisitor extends AbstractJsonFormatVisitorWithSerializerProvider implements JsonArrayFormatVisitor {
+ @Override
+ public void itemsFormat(JsonFormatVisitable handler, JavaType elementType) throws JsonMappingException {
+ log.trace("expectArrayFormat - handler: $handler - elementType: {} - preferredElementType: {}", elementType, preferredElementType);
+ var type = ctx.tryToReMapType(preferredElementType);
+ var visitor = createChildVisitor(itemsNode, null);
+ ctx.objectMapper.acceptJsonFormatVisitor(type, visitor);
+ }
+
+ @Override
+ public void itemsFormat(JsonFormatTypes format) {
+ log.trace("itemsFormat - format: {}", format);
+ itemsNode.put("type", format.value());
+ }
+ }
+
+ return new MyVisitor();
+ }
+
+ @Override
+ public JsonNumberFormatVisitor expectNumberFormat(JavaType type) {
+ log.trace("expectNumberFormat");
+
+ node.put("type", "number");
+
+ // Look for @Min, @Max, @DecimalMin, @DecimalMax => minimum, maximum
+ var minAnnotation = tryGetAnnotation(Min.class);
+ if (minAnnotation != null)
+ node.put("minimum", minAnnotation.value());
+
+ var maxAnnotation = tryGetAnnotation(Max.class);
+ if (maxAnnotation != null)
+ node.put("maximum", maxAnnotation.value());
+
+ var decimalMinAnnotation = tryGetAnnotation(DecimalMin.class);
+ if (decimalMinAnnotation != null)
+ node.put("minimum", Double.valueOf(decimalMinAnnotation.value()));
+
+ var decimalMaxAnnotation = tryGetAnnotation(DecimalMax.class);
+ if (decimalMaxAnnotation != null)
+ node.put("maximum", Double.valueOf(decimalMaxAnnotation.value()));
+
+ var defaultValue = extractDefaultValue();
+ if (defaultValue != null)
+ node.put("default", Double.valueOf(defaultValue));
+
+ if (currentProperty != null) {
+ var examplesAnnotation = currentProperty.getAnnotation(JsonSchemaExamples.class);
+ if (examplesAnnotation != null) {
+ ArrayNode examples = JsonNodeFactory.instance.arrayNode();
+ for (var example : examplesAnnotation.value()) {
+ examples.add(example);
+ }
+ node.set("examples", examples);
+ }
+ }
+
+ return new MyJsonValueFormatVisitor();
+ }
+
+ @Override
+ public JsonAnyFormatVisitor expectAnyFormat(JavaType type) {
+ log.warn("Unable to process {} - "
+ + "it is probably using custom serializer which does not override acceptJsonFormatVisitor", type);
+
+ return new JsonAnyFormatVisitor() {};
+ }
+
+ @Override
+ public JsonIntegerFormatVisitor expectIntegerFormat(JavaType type) {
+ log.trace("expectIntegerFormat");
+
+ node.put("type", "integer");
+
+ var minAnnotation = tryGetAnnotation(Min.class);
+ if (minAnnotation != null)
+ node.put("minimum", minAnnotation.value());
+
+ var maxAnnotation = tryGetAnnotation(Max.class);
+ if (maxAnnotation != null)
+ node.put("maximum", maxAnnotation.value());
+
+ var defaultValue = extractDefaultValue();
+ if (defaultValue != null)
+ node.put("default", Integer.valueOf(defaultValue));
+
+ if (currentProperty != null) {
+ var examplesAnnotation = currentProperty.getAnnotation(JsonSchemaExamples.class);
+ if (examplesAnnotation != null) {
+ ArrayNode examples = JsonNodeFactory.instance.arrayNode();
+ for (var example : examplesAnnotation.value()) {
+ examples.add(example);
+ }
+ node.set("examples", examples);
+ }
+ }
+
+ return new MyJsonValueFormatVisitor();
+ }
+
+ @Override public JsonNullFormatVisitor expectNullFormat(JavaType type) {
+ log.trace("expectNullFormat {}", type);
+ node.put("type", "null");
+ return new JsonNullFormatVisitor.Base();
+ }
+
+ @Override public JsonBooleanFormatVisitor expectBooleanFormat(JavaType type) {
+ log.trace("expectBooleanFormat");
+
+ node.put("type", "boolean");
+
+ var defaultValue = extractDefaultValue();
+ if (defaultValue != null)
+ node.put("default", Boolean.valueOf(defaultValue));
+
+ return new MyJsonValueFormatVisitor();
+ }
+
+ @Override public JsonMapFormatVisitor expectMapFormat(JavaType type) throws JsonMappingException {
+ log.trace ("expectMapFormat {}", type);
+
+ // There is no way to specify map in jsonSchema,
+ // So we're going to treat it as type=object with additionalProperties = true,
+ // so that it can hold whatever the map can hold
+
+ node.put("type", "object");
+
+ // If we're annotated with @NotEmpty, make sure we add a minItems of 1 to our schema here.
+ var notEmptyAnnotation = tryGetAnnotation(NotEmpty.class);
+ if (notEmptyAnnotation != null)
+ node.put("minProperties", 1);
+
+ var defaultValue = extractDefaultValue();
+ if (defaultValue != null)
+ node.put("default", defaultValue);
+
+ var additionalPropsObject = JsonNodeFactory.instance.objectNode();
+ definitionsHandler.pushWorkInProgress();
+ var childVisitor = createChildVisitor(additionalPropsObject, null);
+ ctx.objectMapper.acceptJsonFormatVisitor(ctx.tryToReMapType(type.getContentType()), childVisitor);
+ definitionsHandler.popworkInProgress();
+ node.set("additionalProperties", additionalPropsObject);
+
+
+ class MapVisitor extends AbstractJsonFormatVisitorWithSerializerProvider implements JsonMapFormatVisitor {
+
+ @Override public void keyFormat(JsonFormatVisitable handler, JavaType keyType) {
+ log.trace("JsonMapFormatVisitor.keyFormat handler: $handler - keyType: $keyType");
+ }
+
+ @Override public void valueFormat(JsonFormatVisitable handler, JavaType valueType) {
+ log.trace("JsonMapFormatVisitor.valueFormat handler: $handler - valueType: $valueType");
+ }
+ }
+
+ return new MapVisitor();
+ }
+
+ record PolymorphismInfo(String typePropertyName, String subTypeName) {}
+
+ private PolymorphismInfo extractPolymorphismInfo(JavaType type) throws JsonMappingException {
+
+ var baseType = Utils.getSuperClass(type);
+ if (baseType == null)
+ return null;
+
+ var serializer = ctx.getTypeSerializer(baseType);
+ if (serializer == null)
+ return null;
+
+ var inclusionMethod = serializer.getTypeInclusion();
+ if (inclusionMethod == JsonTypeInfo.As.PROPERTY
+ || inclusionMethod == JsonTypeInfo.As.EXISTING_PROPERTY) {
+ var idResolver = serializer.getTypeIdResolver();
+ assert idResolver != null;
+ String id;
+ if (idResolver instanceof MinimalClassNameIdResolver)
+ // use custom implementation, because default implementation needs instance and we don't have one
+ id = Utils.extractMinimalClassnameId(baseType, type);
+ else
+ id = idResolver.idFromValueAndType(null, type.getRawClass());
+ return new PolymorphismInfo(serializer.getPropertyName(), id);
+ }
+ else
+ throw new IllegalStateException("We do not support polymorphism using jsonTypeInfo.include() = " + inclusionMethod);
+ }
+
+ private List> extractSubTypes(JavaType type) {
+ return extractSubTypes(type.getRawClass());
+ }
+
+ private List> extractSubTypes(Class> type) {
+ var ac = AnnotatedClassResolver.resolveWithoutSuperTypes(
+ ctx.objectMapper.getDeserializationConfig(),
+ type,
+ ctx.objectMapper.getDeserializationConfig());
+
+ var jsonTypeInfo = ac.getAnnotation(JsonTypeInfo.class);
+ if (jsonTypeInfo == null) {
+ return List.of();
+ }
+
+ if (jsonTypeInfo.use() == JsonTypeInfo.Id.NAME) {
+
+ var subTypeAnn = type.getDeclaredAnnotation(JsonSubTypes.class);
+
+ if (subTypeAnn == null) {
+ // We did not find it via @JsonSubTypes-annotation (Probably since it is using mixin's) => Must fallback to using collectAndResolveSubtypesByClass
+ var resolvedSubTypes
+ = ctx.objectMapper.getSubtypeResolver()
+ .collectAndResolveSubtypesByClass(ctx.objectMapper.getDeserializationConfig(), ac);
+
+ return resolvedSubTypes.stream()
+ .map(e -> e.getType())
+ .filter(c -> type.isAssignableFrom(c) && type != c)
+// .toList(); // javac bug, lol (#9072339)
+ .collect(Collectors.toList());
+ }
+
+
+ var subTypes = subTypeAnn.value();
+ return Stream.of(subTypes)
+ .map(subType -> subType.value())
+ // Who the hell thought of multiMap? God I hate Streams
+ // Also, another javac bug, lol (#9072340)
+// .mapMulti((subType, consumer) -> {
+ .>mapMulti((subType, consumer) -> {
+ var subSubTypes = extractSubTypes(subType);
+ if (!subSubTypes.isEmpty())
+ for (var subSubType : subSubTypes)
+ consumer.accept(subSubType);
+ else
+ consumer.accept(subType);
+ })
+ .toList();
+ }
+ else
+ return ctx.config.subclassesResolver.getSubclasses(type);
+ }
+
+ @Override
+ public JsonObjectFormatVisitor expectObjectFormat(JavaType type) throws JsonMappingException {
+
+ var defaultValue = extractDefaultValue();
+ if (defaultValue != null)
+ node.put("default", defaultValue);
+
+ List> subTypes = extractSubTypes(type);
+
+ // Check if we have subtypes
+ if (!subTypes.isEmpty()) {
+ // We have subtypes
+ //log.trace("polymorphism - subTypes: $subTypes")
+
+ var anyOfArrayNode = JsonNodeFactory.instance.arrayNode();
+ node.set("oneOf", anyOfArrayNode);
+
+ for (var subType : subTypes) {
+ log.trace("polymorphism - subType: $subType");
+ var definitionInfo = definitionsHandler.getOrCreateDefinition
+ (ctx.objectMapper.constructType(subType),
+ (t, objectNode) -> {
+ var childVisitor = createChildVisitor(objectNode, null);
+ ctx.objectMapper.acceptJsonFormatVisitor(ctx.tryToReMapType(subType), childVisitor);
+ return null;
+ });
+
+ var thisOneOfNode = JsonNodeFactory.instance.objectNode();
+ thisOneOfNode.put("$ref", definitionInfo.ref());
+
+ // If class is annotated with JsonSchemaTitle, we should add it
+ var titleAnnotation = subType.getDeclaredAnnotation(JsonSchemaTitle.class);
+ if (titleAnnotation != null)
+ thisOneOfNode.put("title", titleAnnotation.value());
+
+ anyOfArrayNode.add(thisOneOfNode);
+ }
+
+ return null; // Returning null to stop jackson from visiting this object since we have done it manually
+ }
+ else {
+ // We do not have subtypes
+
+ if (level == 0) {
+ // This is the first level - we must not use definitions
+ return objectBuilder(type, node);
+ }
+ else {
+ DefinitionInfo definitionInfo = definitionsHandler.getOrCreateDefinition(type, this::objectBuilder);
+
+ if (definitionInfo.ref() != null)
+ node.put("$ref", definitionInfo.ref());
+
+ return definitionInfo.jsonObjectFormatVisitor();
+ }
+ }
+ }
+
+
+ private JsonObjectFormatVisitor objectBuilder(JavaType type, ObjectNode thisObjectNode) throws JsonMappingException {
+
+ thisObjectNode.put("type", "object");
+ thisObjectNode.put("additionalProperties", !ctx.config.failOnUnknownProperties);
+
+ var ac = AnnotatedClassResolver.resolve(ctx.objectMapper.getDeserializationConfig(), type, ctx.objectMapper.getDeserializationConfig());
+
+ // If class is annotated with JsonSchemaFormat, we should add it
+ var format = ctx.resolvePropertyFormat(type);
+ if (format != null)
+ thisObjectNode.put("format", format);
+
+ // If class is annotated with JsonSchemaDescription, we should add it
+ var descriptionAnnotation = ac.getAnnotations().get(JsonSchemaDescription.class);
+ if (descriptionAnnotation != null)
+ thisObjectNode.put("description", descriptionAnnotation.value());
+ else {
+ var descriptionAnnotation2 = ac.getAnnotations().get(JsonPropertyDescription.class);
+ if (descriptionAnnotation2 != null)
+ thisObjectNode.put("description", descriptionAnnotation2.value());
+ }
+
+// // Scala (just as long. wtf?)
+// Option(ac.getAnnotations.get(classOf[JsonSchemaDescription])).map(_.value())
+// .orElse(Option(ac.getAnnotations.get(classOf[JsonPropertyDescription])).map(_.value))
+// .foreach {
+// description: String =>
+// thisObjectNode.put("description", description)
+// }
+
+// // Hypothetical syntax
+// var description = (ac.getAnnotations().get(JsonSchemaDescription.class) ?| _.value())
+// ?? (ac.getAnnotations().get(JsonPropertyDescription.class) ?| _.value());
+// description ?| thisObjectNode.put("description", _);
+
+// // Alt syntax
+// var description = ac.getAnnotations().get(JsonSchemaDescription.class)?.value()
+// description ??= ac.getAnnotations().get(JsonPropertyDescription.class)?.value();
+// if (description) thisObjectNode.put("description", description);
+
+ // If class is annotated with JsonSchemaTitle, we should add it
+ var titleAnnotation = ac.getAnnotations().get(JsonSchemaTitle.class);
+ if (titleAnnotation != null)
+ thisObjectNode.put("title", titleAnnotation.value());
+
+// // alt syntax
+// ac.getAnnotations().get(JsonSchemaTitle.class) ?| thisObjectNode.put("title", _.value());
+
+ // If class is annotated with JsonSchemaOptions, we should add it
+ var optionsAnnotation = ac.getAnnotations().get(JsonSchemaOptions.class);
+ if (optionsAnnotation != null) {
+ var optionsNode = Utils.getOptionsNode(thisObjectNode);
+ for (var item : optionsAnnotation.items())
+ optionsNode.put(item.name(), item.value());
+ }
+
+
+ // Add JsonSchemaInject to top-level, if exists.
+ // Possibly, it overrides further processing.
+ boolean injectOverridesAll;
+ var injectAnnotation = ctx.selectAnnotation(ac, JsonSchemaInject.class);
+ if (injectAnnotation != null) {
+ // Continue to render props if we merged injection
+ injectOverridesAll = ctx.injectFromAnnotation(thisObjectNode, injectAnnotation);
+ }
+ else
+ injectOverridesAll = false;
+ if (injectOverridesAll)
+ return null;
+
+
+ var propertiesNode = Utils.getOrCreateObjectChild(thisObjectNode, "properties");
+
+ var polyInfo = extractPolymorphismInfo(type);
+ if (polyInfo != null) {
+ thisObjectNode.put("title", polyInfo.subTypeName);
+
+ // must inject the 'type'-param and value as enum with only one possible value
+ // This is done to make sure the json generated from the schema using this oneOf
+ // contains the correct "type info"
+ var enumValuesNode = JsonNodeFactory.instance.arrayNode();
+ enumValuesNode.add(polyInfo.subTypeName);
+
+ var enumObjectNode = Utils.getOrCreateObjectChild(propertiesNode, polyInfo.typePropertyName);
+ enumObjectNode.put("type", "string");
+ enumObjectNode.set("enum", enumValuesNode);
+ enumObjectNode.put("default", polyInfo.subTypeName);
+
+ if (ctx.config.hidePolymorphismTypeProperty) {
+ // Make sure the editor hides this polymorphism-specific property
+ var optionsNode = JsonNodeFactory.instance.objectNode();
+ enumObjectNode.set("options", optionsNode);
+ optionsNode.put("hidden", true);
+ }
+
+ Utils.getRequiredArrayNode(thisObjectNode).add(polyInfo.typePropertyName);
+
+ if (ctx.config.useMultipleEditorSelectViaProperty) {
+ // https://github.com/jdorn/json-editor/issues/709
+ // Generate info to help generated editor to select correct oneOf-type
+ // when populating the gui/schema with existing data
+ var objectOptionsNode = Utils.getOrCreateObjectChild( thisObjectNode, "options");
+ var multipleEditorSelectViaPropertyNode = Utils.getOrCreateObjectChild( objectOptionsNode, "multiple_editor_select_via_property");
+ multipleEditorSelectViaPropertyNode.put("property", polyInfo.typePropertyName);
+ multipleEditorSelectViaPropertyNode.put("value", polyInfo.subTypeName);
+ }
+
+ }
+
+ class MyObjectVisitor extends AbstractJsonFormatVisitorWithSerializerProvider implements JsonObjectFormatVisitor {
+
+ @Override public void optionalProperty(BeanProperty prop) throws JsonMappingException {
+ log.trace("JsonObjectFormatVisitor.optionalProperty: prop: {}", prop);
+ handleProperty(prop.getName(), prop.getType(), prop, false);
+ }
+
+ @Override public void optionalProperty(String name, JsonFormatVisitable handler, JavaType propertyTypeHint) throws JsonMappingException {
+ log.trace("JsonObjectFormatVisitor.optionalProperty: name:{} handler:{} propertyTypeHint:{}", name, handler, propertyTypeHint);
+ handleProperty(name, propertyTypeHint, null, false);
+ }
+
+ @Override public void property(BeanProperty prop) throws JsonMappingException {
+ log.trace("JsonObjectFormatVisitor.property: prop:{}", prop);
+ handleProperty(prop.getName(), prop.getType(), prop, true);
+ }
+
+ @Override public void property(String name, JsonFormatVisitable handler, JavaType propertyTypeHint) throws JsonMappingException {
+ log.trace("JsonObjectFormatVisitor.property: name:{} handler:{} propertyTypeHint:{}", name, handler, propertyTypeHint);
+ handleProperty(name, propertyTypeHint, null, true);
+ }
+
+ // Used when rendering schema using propertyOrdering as specified here:
+ // https://github.com/jdorn/json-editor#property-ordering
+ int nextPropertyOrderIndex = 1;
+
+ void handleProperty(String propertyName, JavaType propertyType, BeanProperty prop, Boolean jsonPropertyRequired) throws JsonMappingException {
+ log.trace("JsonObjectFormatVisitor - {}: {}", propertyName, propertyType);
+
+ if (propertiesNode.get(propertyName) != null) {
+ log.debug("Ignoring property '{}' in $propertyType since it has already been added, probably as type-property using polymorphism", propertyName);
+ return;
+ }
+
+ // Need to check for Optional/Optional-special-case before we know what node to use here.
+ record PropertyNode(ObjectNode main, ObjectNode meta) {}
+
+ // Check if we should set this property as required. Primitive types MUST have a value, as does anything
+ // with a @JsonProperty that has "required" set to true. Lastly, various javax.validation annotations also
+ // make this required.
+ boolean requiredProperty = propertyType.getRawClass().isPrimitive() || jsonPropertyRequired || ctx.validationAnnotationRequired(prop);
+
+ boolean optionalType = Optional.class.isAssignableFrom(propertyType.getRawClass())
+ || propertyType.getRawClass().getName().equals("scala.Option");
+
+ PropertyNode thisPropertyNode;
+ {
+ var node = JsonNodeFactory.instance.objectNode();
+ propertiesNode.set(propertyName, node);
+
+ if (ctx.config.usePropertyOrdering) {
+ node.put("propertyOrder", nextPropertyOrderIndex);
+ nextPropertyOrderIndex += 1;
+ }
+
+ if (!requiredProperty && ((ctx.config.useOneOfForOption && optionalType) ||
+ (ctx.config.useOneOfForNullables && !optionalType))) {
+ // We support this type being null, insert a oneOf consisting of a sentinel "null" and the real type.
+ var oneOfArray = JsonNodeFactory.instance.arrayNode();
+ node.set("oneOf", oneOfArray);
+
+ // Create our sentinel "null" value for the case no value is provided.
+ var oneOfNull = JsonNodeFactory.instance.objectNode();
+ oneOfNull.put("type", "null");
+ oneOfNull.put("title", "Not included");
+ oneOfArray.add(oneOfNull);
+
+ // If our nullable/optional type has a value, it'll be this.
+ var oneOfReal = JsonNodeFactory.instance.objectNode();
+ oneOfArray.add(oneOfReal);
+
+ // Return oneOfReal which, from now on, will be used as the node representing this property
+ thisPropertyNode = new PropertyNode(oneOfReal, node);
+ } else {
+ // Our type must not be null: primitives, @NotNull annotations, @JsonProperty annotations marked required etc.
+ thisPropertyNode = new PropertyNode(node, node);
+ }
+ }
+
+ // Continue processing this property
+ var childVisitor = createChildVisitor(thisPropertyNode.main, prop);
+
+ // Push current work in progress since we're about to start working on a new class
+ definitionsHandler.pushWorkInProgress();
+
+ if ((Optional.class.isAssignableFrom(propertyType.getRawClass())
+ || propertyType.getRawClass().getName().equals("scala.Option"))
+ && propertyType.containedTypeCount() >= 1) {
+
+ // Property is scala Optional or Java Optional.
+ //
+ // Due to Java's Type Erasure, the type behind Optional is lost.
+ // To workaround this, we use the same workaround as jackson-scala-module described here:
+ // https://github.com/FasterXML/jackson-module-scala/wiki/FAQ#deserializing-optionint-and-other-primitive-challenges
+
+ JavaType optionType = Utils.resolveElementType(propertyType, prop, ctx.objectMapper);
+
+ ctx.objectMapper.acceptJsonFormatVisitor(ctx.tryToReMapType(optionType), childVisitor);
+ } else {
+ ctx.objectMapper.acceptJsonFormatVisitor(ctx.tryToReMapType(propertyType), childVisitor);
+ }
+
+ // Pop back the work we were working on..
+ definitionsHandler.popworkInProgress();
+
+ // If this property is required, add it to our array of required properties.
+ if (requiredProperty)
+ Utils.getRequiredArrayNode(thisObjectNode).add(propertyName);
+
+ if (prop == null)
+ return;
+
+ var format = ctx.resolvePropertyFormat(prop);
+ if (format != null)
+ thisPropertyNode.main.put("format", format);
+
+ // Optionally add description
+ var descriptionAnn = prop.getAnnotation(JsonSchemaDescription.class);
+ var descriptionAnn2 = prop.getAnnotation(JsonPropertyDescription.class);
+ if (descriptionAnn != null)
+ thisPropertyNode.meta.put("description", descriptionAnn.value());
+ else if (descriptionAnn2 != null)
+ thisPropertyNode.meta.put("description", descriptionAnn2.value());
+
+ // Optionally add title
+ var titleAnn = prop.getAnnotation(JsonSchemaTitle.class);
+ if (titleAnn != null)
+ thisPropertyNode.meta.put("title", titleAnn.value());
+ else if (ctx.config.autoGenerateTitleForProperties) {
+ // We should generate 'pretty-name' based on propertyName
+ var title = Utils.camelCaseToSentenceCase(propertyName);
+ thisPropertyNode.meta.put("title", title);
+ }
+
+// var title = prop.getAnnotation(JsonSchemaTitle.class) ?| _.value();
+// if (ctx.config.autoGenerateTitleForProperties)
+// title ??= Utils.generateTitleFromPropertyName(propertyName);
+// title ?| thisPropertyNode.meta.put("title", _);
+
+ // Optionally add options
+ var optionsAnn = prop.getAnnotation(JsonSchemaOptions.class);
+ if (optionsAnn != null) {
+ var optionsNode = Utils.getOrCreateObjectChild(thisPropertyNode.meta, "options");
+ for (var option : optionsAnn.items())
+ optionsNode.put(option.name(), option.value());
+ }
+
+ // unpack operator and foreach-pipe
+// prop.getAnnotation(JsonSchemaOptions.class) ?| *(_.items()) | option -> {
+// var optionsNode = Utils.getOrCreateObjectChild(thisPropertyNode.meta, "options");
+// optionsNode.put(option.name(), option.value());
+// };
+
+ // just null-safe (or null short-circuiting) pipe
+// for (var option : (prop.getAnnotation(JsonSchemaOptions.class) ?| _.items()) ?? List.of()) {
+// var optionsNode = Utils.getOrCreateObjectChild(thisPropertyNode.meta, "options");
+// optionsNode.put(_.name(), _.value());
+// }
+
+ // null-safe foreach (possibly using :? operator)
+// for (var option : prop.getAnnotation(JsonSchemaOptions.class) ?| _.items()) {
+// var optionsNode = Utils.getOrCreateObjectChild(thisPropertyNode.meta, "options");
+// optionsNode.put(_.name(), _.value());
+// }
+
+ // Optionally add JsonSchemaInject
+ var injectAnn = ctx.selectAnnotation(prop, JsonSchemaInject.class);
+ if (injectAnn == null) {
+ // Try to look at the class itself -- Looks like this is the only way to find it if the type is Enum
+ var injectAnn2 = prop.getType().getRawClass().getAnnotation(JsonSchemaInject.class);
+ if (injectAnn2 != null && ctx.annotationIsApplicable(injectAnn2))
+ injectAnn = injectAnn2;
+ }
+ if (injectAnn != null)
+ ctx.injectFromAnnotation(thisPropertyNode.meta, injectAnn);
+ }
+ }
+
+ return new MyObjectVisitor();
+ }
+}
diff --git a/src/main/java/com/kjetland/jackson/jsonSchema/SubclassesResolver.java b/src/main/java/com/kjetland/jackson/jsonSchema/SubclassesResolver.java
new file mode 100644
index 0000000..d7dc9f4
--- /dev/null
+++ b/src/main/java/com/kjetland/jackson/jsonSchema/SubclassesResolver.java
@@ -0,0 +1,52 @@
+package com.kjetland.jackson.jsonSchema;
+
+import io.github.classgraph.ClassGraph;
+import io.github.classgraph.ScanResult;
+import java.util.List;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class SubclassesResolver {
+
+ private ScanResult scanResult;
+
+ public SubclassesResolver() {
+ this(null);
+ }
+
+ public SubclassesResolver (List packagesToScan, List classesToScan) {
+ this(buildClassGraph(packagesToScan, classesToScan));
+ }
+
+ public SubclassesResolver (ClassGraph classGraph) {
+ if (classGraph == null) {
+ log.debug("Entire classpath will be scanned because SubclassesResolver is not configured. See "
+ + "https://github.com/mbknor/mbknor-jackson-jsonSchema#subclass-resolving-using-reflection");
+ classGraph = new ClassGraph();
+ }
+
+ scanResult = classGraph.enableClassInfo().scan();
+ }
+
+ public List> getSubclasses(Class> clazz) {
+ if (clazz.isInterface())
+ return scanResult.getClassesImplementing(clazz.getName()).loadClasses();
+ else
+ return scanResult.getSubclasses(clazz.getName()).loadClasses();
+ }
+
+ static private ClassGraph buildClassGraph(List packagesToScan, List classesToScan) {
+ if (packagesToScan == null && classesToScan == null)
+ return null;
+
+ ClassGraph classGraph = new ClassGraph();
+
+ if (packagesToScan != null)
+ classGraph.whitelistPackages(packagesToScan.toArray(String[]::new));
+
+ if (classesToScan != null)
+ classGraph.whitelistClasses(classesToScan.toArray(String[]::new));
+
+ return classGraph;
+ }
+}
diff --git a/src/main/java/com/kjetland/jackson/jsonSchema/Utils.java b/src/main/java/com/kjetland/jackson/jsonSchema/Utils.java
new file mode 100644
index 0000000..386b588
--- /dev/null
+++ b/src/main/java/com/kjetland/jackson/jsonSchema/Utils.java
@@ -0,0 +1,181 @@
+package com.kjetland.jackson.jsonSchema;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.util.ClassUtil;
+import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaInject;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.BiConsumer;
+import java.util.regex.Pattern;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ *
+ * @author alex
+ */
+@Slf4j
+public final class Utils {
+
+ private Utils() {}
+
+ public static String extractMinimalClassnameId(JavaType baseType, JavaType child) {
+ // code taken from Jackson's MinimalClassNameIdResolver constructor and method idFromValue
+
+ var base = baseType.getRawClass().getName();
+ var ix = base.lastIndexOf('.');
+
+ String basePackagePrefix;
+ if (ix < 0) // can this ever occur?
+ basePackagePrefix = ".";
+ else
+ basePackagePrefix = base.substring(0, ix + 1);
+
+ var n = child.getRawClass().getName();
+ if (n.startsWith(basePackagePrefix)) { // note: we will leave the leading dot in there
+ return n.substring(basePackagePrefix.length() - 1);
+ } else {
+ return n;
+ }
+ }
+
+
+ public static void merge(JsonNode mainNode, JsonNode updateNode) {
+ var fieldNames = updateNode.fieldNames();
+ while (fieldNames.hasNext()) {
+ var fieldName = fieldNames.next();
+ var jsonNode = mainNode.get(fieldName);
+ // if field exists and is an embedded object
+ if (jsonNode != null && jsonNode.isObject()) {
+ merge(jsonNode, updateNode.get(fieldName));
+ }
+ else {
+ if (mainNode instanceof ObjectNode node) {
+ // Overwrite field
+ var value = updateNode.get(fieldName);
+ node.set(fieldName, value);
+ }
+ }
+ }
+ }
+
+
+ public static void visit(JsonNode o, String path, BiConsumer f) {
+ var parts = path.split(Pattern.quote("/"));
+ var lastPart = parts[parts.length - 1];
+ var otherParts = Arrays.copyOfRange(parts, 0, parts.length - 1);
+ var p = o;
+ for (var name : otherParts) {
+ var child = p.get(name);
+ if (child == null)
+ child = ((ObjectNode)p).putObject(name);
+ p = child;
+ }
+ f.accept((ObjectNode)p, lastPart);
+ }
+
+ public static String camelCaseToSentenceCase(String propertyName) {
+ // Code found here: http://stackoverflow.com/questions/2559759/how-do-i-convert-camelcase-into-human-readable-names-in-java
+ var s = propertyName.replaceAll(
+ "(?<=[A-Z])(?=[A-Z][a-z])"
+ + "|(?<=[^A-Z])(?=[A-Z])"
+ + "|(?<=[A-Za-z])(?=[^A-Za-z])",
+ " ");
+
+ // Make the first letter uppercase
+ return s.substring(0,1).toUpperCase() + s.substring(1);
+ }
+
+ public static JavaType resolveElementType(JavaType propertyType, BeanProperty prop, ObjectMapper objectMapper) {
+ var containedType = propertyType.containedType(0);
+ if (containedType.getRawClass() == Object.class) {
+ // Scala BS
+ // https://github.com/FasterXML/jackson-module-scala/wiki/FAQ#deserializing-optionint-and-other-primitive-challenges
+ var jsonDeserialize = prop.getAnnotation(JsonDeserialize.class);
+ if (jsonDeserialize != null)
+ return objectMapper.getTypeFactory().constructType(jsonDeserialize.contentAs());
+ else {
+ log.debug("Use @JsonDeserialize(contentAs=, keyAs=) to specify type of collection elements of {}", prop);
+ return containedType;
+ }
+ }
+ else {
+ // use containedType as is
+ return containedType;
+ }
+ }
+
+ public static ArrayNode getRequiredArrayNode(ObjectNode objectNode) {
+ var requiredNode = objectNode.get("required");
+
+ if (requiredNode == null) {
+ requiredNode = JsonNodeFactory.instance.arrayNode();
+ objectNode.set("required", requiredNode);
+ }
+
+ return (ArrayNode) requiredNode;
+ }
+
+ public static ObjectNode getOptionsNode(ObjectNode objectNode) {
+ return getOrCreateObjectChild(objectNode, "options");
+ }
+
+ public static ObjectNode getOrCreateObjectChild(ObjectNode parentObjectNode, String name) {
+ var childNode = parentObjectNode.get(name);
+
+ if (childNode == null) {
+ childNode = JsonNodeFactory.instance.objectNode();
+ parentObjectNode.set(name, childNode);
+ }
+
+ return (ObjectNode) childNode;
+ }
+
+
+ public static String extractTypeName(JavaType type) {
+ // use JsonTypeName annotation if present
+ var annotation = type.getRawClass().getDeclaredAnnotation(JsonTypeName.class);
+ return Optional.ofNullable(annotation)
+ .flatMap(a -> Optional.of(a.value()))
+ .filter(a -> !a.isEmpty())
+ .orElse(type.getRawClass().getSimpleName());
+ }
+
+ public static List> extractGroupsFromAnnotation(Annotation annotation) {
+ // Annotations cannot implement interface, so we have to check each and every
+ // javax-annotation... To prevent bugs with missing groups-extract-impl when new
+ // validation-annotations are added, I've decided to do it using reflection
+ var annotationClass = annotation.annotationType();
+ if (annotationClass.getPackage().getName().startsWith("javax.validation.constraints")) {
+ try {
+ var groups = (Class>[]) annotationClass.getMethod("groups").invoke(annotation);
+ return List.of(groups);
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+ return List.of();
+ }
+ }
+ else if (annotation instanceof JsonSchemaInject _annotation)
+ return List.of(_annotation.javaxValidationGroups());
+ else
+ return List.of();
+ }
+
+ public static JavaType getSuperClass(JavaType type) {
+ for (var superType : ClassUtil.findSuperTypes(type, null, false))
+ if (superType.getRawClass().isAnnotationPresent(JsonTypeInfo.class))
+ return superType;
+ // else
+ return type.getSuperClass();
+ }
+}
diff --git a/src/main/java/com/kjetland/jackson/jsonSchema/annotations/JsonSchemaInject.java b/src/main/java/com/kjetland/jackson/jsonSchema/annotations/JsonSchemaInject.java
index c6dbe88..4dcd754 100755
--- a/src/main/java/com/kjetland/jackson/jsonSchema/annotations/JsonSchemaInject.java
+++ b/src/main/java/com/kjetland/jackson/jsonSchema/annotations/JsonSchemaInject.java
@@ -1,58 +1,59 @@
package com.kjetland.jackson.jsonSchema.annotations;
+import com.kjetland.jackson.jsonSchema.JsonSchemaConfig;
import com.fasterxml.jackson.databind.JsonNode;
-
+import static java.lang.annotation.ElementType.*;
import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
import java.util.function.Supplier;
-import static java.lang.annotation.ElementType.*;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
/**
- * Use this annotation to inject json into the generated jsonSchema.
+ * Use this annotation to inject JSON into the generated jsonSchema.
+ * When applied to a class, will be injected into the object type node.
+ * When applied to a property, will be injected into the property node.
*/
@Target({METHOD, FIELD, PARAMETER, TYPE})
@Retention(RUNTIME)
public @interface JsonSchemaInject {
/**
- * @return a raw json that will be merged on top of the generated jsonSchema
+ * JSON that will injected into the object or property node.
*/
String json() default "{}";
/**
- * @return a class for supplier of a raw json. The json gets applied after {@link #json()}.
+ * Supplier of a JsonNode that will be injected. Applied after {@link #json()}.
*/
Class extends Supplier> jsonSupplier() default None.class;
/**
- * @return a key to lookup a jsonSupplier via lookupMap defined in JsonSchemaConfig
+ * Key to entry in {@link JsonSchemaConfig#jsonSuppliers} which will supply
+ * a JsonNode that will be injected. Applied after {@link #jsonSupplier()}.
*/
String jsonSupplierViaLookup() default "";
/**
- * @return a collection of key/value pairs to merge on top of the generated jsonSchema and applied after {@link #jsonSupplier()}
+ * Collection of String key/value pairs that will be injected. Applied after {@link #jsonSupplierViaLookup()}.
*/
JsonSchemaString[] strings() default {};
/**
- * @return a collection of key/value pairs to merge on top of the generated jsonSchema and applied after {@link #jsonSupplier()
+ * Collection of Integer key/value pairs that will be injected. Applied after {@link #strings()}.
*/
JsonSchemaInt[] ints() default {};
/**
- * @return a collection of key/value pairs to merge on top of the generated jsonSchema and applied after {@link #jsonSupplier()
+ * Collection of Boolean key/value pairs that will be injected. Applied after {@link #ints()}.
*/
JsonSchemaBool[] bools() default {};
/**
- * If merge is true (the default), the injected json will be injected into the generated jsonSchema-node. If merge = false, then
- * we skips the generated jsonSchema-node and use the entire injected one instead.
- * @return whether we should merge or replaceWith the injected json
+ * If overridesAll is false (the default), the injected json will be merged with the generated schema.
+ * If overridesAll is true, then we skip schema generation and use only the injected json.
*/
- boolean merge() default true;
+ boolean overridesAll() default false;
- // This can be used in the same way as 'groups' in javax.validation.constraints, e.g @NotNull
+ // This can be used in the same way as 'groups' in javax.validation.constraints
Class>[] javaxValidationGroups() default { };
class None implements Supplier {
diff --git a/src/main/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGenerator.scala b/src/main/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGenerator.scala
deleted file mode 100755
index 17e2258..0000000
--- a/src/main/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGenerator.scala
+++ /dev/null
@@ -1,1501 +0,0 @@
-package com.kjetland.jackson.jsonSchema
-
-import java.lang.annotation.Annotation
-import java.util
-import java.util.function.Supplier
-import java.util.{Optional, List => JList}
-
-import com.fasterxml.jackson.annotation.{JsonProperty, JsonPropertyDescription, JsonSubTypes, JsonTypeInfo, JsonTypeName}
-import com.fasterxml.jackson.core.JsonParser.NumberType
-import com.fasterxml.jackson.databind._
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize
-import com.fasterxml.jackson.databind.introspect.{AnnotatedClass, AnnotatedClassResolver}
-import com.fasterxml.jackson.databind.jsonFormatVisitors._
-import com.fasterxml.jackson.databind.jsontype.impl.MinimalClassNameIdResolver
-import com.fasterxml.jackson.databind.node.{ArrayNode, JsonNodeFactory, ObjectNode}
-import com.fasterxml.jackson.databind.util.ClassUtil
-import com.kjetland.jackson.jsonSchema.annotations._
-import io.github.classgraph.{ClassGraph, ScanResult}
-import javax.validation.constraints._
-import javax.validation.groups.Default
-import org.slf4j.LoggerFactory
-
-object JsonSchemaGenerator {
-}
-
-object JsonSchemaConfig {
-
- val vanillaJsonSchemaDraft4 = JsonSchemaConfig(
- autoGenerateTitleForProperties = false,
- defaultArrayFormat = None,
- useOneOfForOption = false,
- useOneOfForNullables = false,
- usePropertyOrdering = false,
- hidePolymorphismTypeProperty = false,
- disableWarnings = false,
- useMinLengthForNotNull = false,
- useTypeIdForDefinitionName = false,
- customType2FormatMapping = Map(),
- useMultipleEditorSelectViaProperty = false,
- uniqueItemClasses = Set(),
- classTypeReMapping = Map(),
- jsonSuppliers = Map()
- )
-
- /**
- * Use this configuration if using the JsonSchema to generate HTML5 GUI, eg. by using https://github.com/jdorn/json-editor
- *
- * autoGenerateTitleForProperties - If property is named "someName", we will add {"title": "Some Name"}
- * defaultArrayFormat - this will result in a better gui than te default one.
- */
- val html5EnabledSchema = JsonSchemaConfig(
- autoGenerateTitleForProperties = true,
- defaultArrayFormat = Some("table"),
- useOneOfForOption = true,
- useOneOfForNullables = false,
- usePropertyOrdering = true,
- hidePolymorphismTypeProperty = true,
- disableWarnings = false,
- useMinLengthForNotNull = true,
- useTypeIdForDefinitionName = false,
- customType2FormatMapping = Map[String,String](
- // Java7 dates
- "java.time.LocalDateTime" -> "datetime-local",
- "java.time.OffsetDateTime" -> "datetime",
- "java.time.LocalDate" -> "date",
-
- // Joda-dates
- "org.joda.time.LocalDate" -> "date"
- ),
- useMultipleEditorSelectViaProperty = true,
- uniqueItemClasses = Set(
- classOf[scala.collection.immutable.Set[_]],
- classOf[scala.collection.mutable.Set[_]],
- classOf[java.util.Set[_]]
- ),
- classTypeReMapping = Map(),
- jsonSuppliers = Map()
- )
-
- /**
- * This configuration is exactly like the vanilla JSON schema generator, except that "nullables" have been turned on:
- * `useOneOfForOption` and `useOneForNullables` have both been set to `true`. With this configuration you can either
- * use `Optional` or `Option`, or a standard nullable Java type and get back a schema that allows nulls.
- *
- *
- * If you need to mix nullable and non-nullable types, you may override the nullability of the type by either setting
- * a `NotNull` annotation on the given property, or setting the `required` attribute of the `JsonProperty` annotation.
- */
- val nullableJsonSchemaDraft4 = JsonSchemaConfig (
- autoGenerateTitleForProperties = false,
- defaultArrayFormat = None,
- useOneOfForOption = true,
- useOneOfForNullables = true,
- usePropertyOrdering = false,
- hidePolymorphismTypeProperty = false,
- disableWarnings = false,
- useMinLengthForNotNull = false,
- useTypeIdForDefinitionName = false,
- customType2FormatMapping = Map(),
- useMultipleEditorSelectViaProperty = false,
- uniqueItemClasses = Set(),
- classTypeReMapping = Map(),
- jsonSuppliers = Map()
- )
-
- // Java-API
- def create(
- autoGenerateTitleForProperties:Boolean,
- defaultArrayFormat:Optional[String],
- useOneOfForOption:Boolean,
- useOneOfForNullables:Boolean,
- usePropertyOrdering:Boolean,
- hidePolymorphismTypeProperty:Boolean,
- disableWarnings:Boolean,
- useMinLengthForNotNull:Boolean,
- useTypeIdForDefinitionName:Boolean,
- customType2FormatMapping:java.util.Map[String, String],
- useMultipleEditorSelectViaProperty:Boolean,
- uniqueItemClasses:java.util.Set[Class[_]],
- classTypeReMapping:java.util.Map[Class[_], Class[_]],
- jsonSuppliers:java.util.Map[String, Supplier[JsonNode]],
- subclassesResolver:SubclassesResolver,
- failOnUnknownProperties:Boolean,
- javaxValidationGroups:java.util.List[Class[_]]
- ):JsonSchemaConfig = {
-
- import scala.collection.JavaConverters._
-
- JsonSchemaConfig(
- autoGenerateTitleForProperties,
- Option(defaultArrayFormat.orElse(null)),
- useOneOfForOption,
- useOneOfForNullables,
- usePropertyOrdering,
- hidePolymorphismTypeProperty,
- disableWarnings,
- useMinLengthForNotNull,
- useTypeIdForDefinitionName,
- customType2FormatMapping.asScala.toMap,
- useMultipleEditorSelectViaProperty,
- uniqueItemClasses.asScala.toSet,
- classTypeReMapping.asScala.toMap,
- jsonSuppliers.asScala.toMap,
- Option(subclassesResolver).getOrElse( new SubclassesResolverImpl()),
- failOnUnknownProperties,
- if (javaxValidationGroups == null) Array[Class[_]]() else {
- javaxValidationGroups.toArray.asInstanceOf[Array[Class[_]]]
- }
- )
- }
-
-}
-
-trait SubclassesResolver {
- def getSubclasses(clazz:Class[_]):List[Class[_]]
-}
-
-case class SubclassesResolverImpl
-(
- classGraph:Option[ClassGraph] = None,
- packagesToScan:List[String] = List(),
- classesToScan:List[String] = List()
-) extends SubclassesResolver {
- import scala.collection.JavaConverters._
-
- def this() = this(None, List(), List())
-
- def withClassGraph(classGraph:ClassGraph):SubclassesResolverImpl = {
- this.copy(classGraph = Option(classGraph))
- }
-
- // Scala API
- def withPackagesToScan(packagesToScan:List[String]):SubclassesResolverImpl = {
- this.copy(packagesToScan = packagesToScan)
- }
-
- // Java API
- def withPackagesToScan(packagesToScan:JList[String]):SubclassesResolverImpl = {
- this.copy(packagesToScan = packagesToScan.asScala.toList)
- }
-
- // Scala API
- def withClassesToScan(classesToScan:List[String]):SubclassesResolverImpl = {
- this.copy(classesToScan = classesToScan)
- }
-
- // Java API
- def withClassesToScan(classesToScan:JList[String]):SubclassesResolverImpl = {
- this.copy(classesToScan = classesToScan.asScala.toList)
- }
-
- lazy val reflection:ScanResult = {
-
- var classGraphConfigured:Boolean = false
-
- if ( classGraph.isDefined ) {
- classGraphConfigured = true
- }
-
- val _classGraph:ClassGraph = classGraph.getOrElse( new ClassGraph() )
-
- if (packagesToScan.nonEmpty) {
- classGraphConfigured = true
- _classGraph.whitelistPackages( packagesToScan:_* )
- }
-
- if ( classesToScan.nonEmpty ) {
- classGraphConfigured = true
- _classGraph.whitelistClasses( classesToScan:_* )
- }
-
- if ( !classGraphConfigured ) {
- LoggerFactory.getLogger(this.getClass).warn(s"Performance-warning. Since SubclassesResolver is not configured," +
- s" it scans the entire classpath. " +
- s"https://github.com/mbknor/mbknor-jackson-jsonSchema#subclass-resolving-using-reflection")
- }
-
- _classGraph.enableClassInfo().scan()
- }
-
- override def getSubclasses(clazz: Class[_]): List[Class[_]] = {
- if (clazz.isInterface)
- reflection.getClassesImplementing(clazz.getName).loadClasses().asScala.toList
- else
- reflection.getSubclasses(clazz.getName).loadClasses().asScala.toList
- }
-}
-
-case class JsonSchemaConfig
-(
- autoGenerateTitleForProperties:Boolean,
- defaultArrayFormat:Option[String],
- useOneOfForOption:Boolean,
- useOneOfForNullables:Boolean,
- usePropertyOrdering:Boolean,
- hidePolymorphismTypeProperty:Boolean,
- disableWarnings:Boolean,
- useMinLengthForNotNull:Boolean,
- useTypeIdForDefinitionName:Boolean,
- customType2FormatMapping:Map[String, String],
- useMultipleEditorSelectViaProperty:Boolean, // https://github.com/jdorn/json-editor/issues/709
- uniqueItemClasses:Set[Class[_]], // If rendering array and type is instanceOf class in this set, then we add 'uniqueItems": true' to schema - See // https://github.com/jdorn/json-editor for more info
- classTypeReMapping:Map[Class[_], Class[_]], // Can be used to prevent rendering using polymorphism for specific classes.
- jsonSuppliers:Map[String, Supplier[JsonNode]], // Suppliers in this map can be accessed using @JsonSchemaInject(jsonSupplierViaLookup = "lookupKey")
- subclassesResolver:SubclassesResolver = new SubclassesResolverImpl(), // Using default impl that scans entire classpath
- failOnUnknownProperties:Boolean = true,
- javaxValidationGroups:Array[Class[_]] = Array(), // Used to match against different validation-groups (javax.validation.constraints)
- jsonSchemaDraft:JsonSchemaDraft = JsonSchemaDraft.DRAFT_04
-) {
-
- def withFailOnUnknownProperties(failOnUnknownProperties:Boolean):JsonSchemaConfig = {
- this.copy( failOnUnknownProperties = failOnUnknownProperties )
- }
-
- def withSubclassesResolver(subclassesResolver: SubclassesResolver):JsonSchemaConfig = {
- this.copy( subclassesResolver = subclassesResolver )
- }
-
- def withJavaxValidationGroups(javaxValidationGroups:Array[Class[_]]):JsonSchemaConfig = {
- this.copy(javaxValidationGroups = javaxValidationGroups)
- }
-
- def withJsonSchemaDraft(jsonSchemaDraft:JsonSchemaDraft):JsonSchemaConfig = {
- this.copy(jsonSchemaDraft = jsonSchemaDraft)
- }
-}
-
-
-
-/**
- * Json Schema Generator
- * @param rootObjectMapper pre-configured ObjectMapper
- * @param debug Default = false - set to true if generator should log some debug info while generating the schema
- * @param config default = vanillaJsonSchemaDraft4. Please use html5EnabledSchema if generating HTML5 GUI, e.g. using https://github.com/jdorn/json-editor
- */
-class JsonSchemaGenerator
-(
- val rootObjectMapper: ObjectMapper,
- debug:Boolean = false,
- config:JsonSchemaConfig = JsonSchemaConfig.vanillaJsonSchemaDraft4
-) {
-
- val javaxValidationGroups = config.javaxValidationGroups
-
- // Java API
- def this(rootObjectMapper: ObjectMapper) = this(rootObjectMapper, false, JsonSchemaConfig.vanillaJsonSchemaDraft4)
-
- // Java API
- def this(rootObjectMapper: ObjectMapper, config:JsonSchemaConfig) = this(rootObjectMapper, false, config)
-
- import scala.collection.JavaConverters._
-
- val log = LoggerFactory.getLogger(getClass)
-
- val dateFormatMapping = Map[String,String](
- // Java7 dates
- "java.time.LocalDateTime" -> "datetime-local",
- "java.time.OffsetDateTime" -> "datetime",
- "java.time.LocalDate" -> "date",
-
- // Joda-dates
- "org.joda.time.LocalDate" -> "date"
- )
-
- trait MySerializerProvider {
- var provider: SerializerProvider = null
-
- def getProvider: SerializerProvider = provider
- def setProvider(provider: SerializerProvider): Unit = this.provider = provider
- }
-
- trait EnumSupport {
-
- val _node: ObjectNode
-
- def enumTypes(enums: util.Set[String]): Unit = {
- // l(s"JsonStringFormatVisitor-enum.enumTypes: ${enums}")
-
- val enumValuesNode = JsonNodeFactory.instance.arrayNode()
- _node.set("enum", enumValuesNode)
-
- enums.asScala.foreach {
- enumValue =>
- enumValuesNode.add(enumValue)
- }
- }
- }
-
- private def setFormat(node:ObjectNode, format:String): Unit = {
- node.put("format", format)
- }
-
- // Verifies that the annotation is applicable based on the config.javaxValidationGroups
- private def annotationIsApplicable(annotation:Annotation):Boolean = {
-
- def extractGroupsFromAnnotation(annotation:Annotation):Array[Class[_]] = {
- // Annotations cannot implement interface, so we have to check each and every
- // javax-annotation... To prevent bugs with missing groups-extract-impl when new
- // validation-annotations are added, I've decided to do it using reflection
- val annotationClass = annotation.annotationType()
- if ( annotationClass.getPackage.getName().startsWith("javax.validation.constraints") ) {
- val groupsMethod = try {
- annotationClass.getMethod("groups")
- } catch {
- case e:NoSuchMethodException => null
- }
- if ( groupsMethod != null ) {
- groupsMethod.invoke(annotation).asInstanceOf[Array[Class[_]]]
- } else {
- Array()
- }
- } else {
- annotation match {
- case x:JsonSchemaInject => x.javaxValidationGroups()
- case _ => Array()
- }
- }
- }
-
- val javaxDefaultGroup = classOf[Default]
-
- val groupsOnAnnotation:Array[Class[_]] = extractGroupsFromAnnotation(annotation)
-
- (javaxValidationGroups, groupsOnAnnotation) match {
- case (Array(), Array()) => true
- case (Array(), l) => l.contains(javaxDefaultGroup)// Use it if groupsOnAnnotation contains Default
- case (l, Array()) => l.contains(javaxDefaultGroup)// Use it if javaxValidationGroups contains Default
- case (a, b) => a.exists( c => b.contains(c))// One of a must be included in b
- }
- }
-
- // Tries to retrieve a annotation and validates that it is applicable
- private def selectAnnotation[T <: Annotation](property:BeanProperty, annotationClass:Class[T]):Option[T] = {
- Option(property.getAnnotation(annotationClass))
- .filter(annotationIsApplicable(_))
- }
-
- // Tries to retrieve a annotation and validates that it is applicable
- private def selectAnnotation[T <: Annotation](annotatedClass:AnnotatedClass, annotationClass:Class[T]):Option[T] = {
- Option(annotatedClass.getAnnotation(annotationClass))
- .filter(annotationIsApplicable(_))
- }
-
-
- case class DefinitionInfo(ref:Option[String], jsonObjectFormatVisitor: Option[JsonObjectFormatVisitor])
-
- // Class that manages creating new definitions or getting $refs to existing definitions
- class DefinitionsHandler() {
- private var class2Ref = Map[JavaType, String]()
- private val definitionsNode = JsonNodeFactory.instance.objectNode()
-
-
- case class WorkInProgress(typeInProgress:JavaType, nodeInProgress:ObjectNode)
-
- // Used when 'combining' multiple invocations to getOrCreateDefinition when processing polymorphism.
- private var workInProgress:Option[WorkInProgress] = None
-
- private var workInProgressStack = List[Option[WorkInProgress]]()
-
- def pushWorkInProgress(): Unit ={
- workInProgressStack = workInProgress :: workInProgressStack
- workInProgress = None
- }
-
- def popworkInProgress(): Unit ={
- workInProgress = workInProgressStack.head
- workInProgressStack = workInProgressStack.tail
- }
-
- def extractTypeName(_type:JavaType) : String = {
- // use JsonTypeName annotation if present
- val annotation = _type.getRawClass.getDeclaredAnnotation(classOf[JsonTypeName])
- Option(annotation).flatMap( a => Option(a.value())).filter(_.nonEmpty)
- .getOrElse( _type.getRawClass.getSimpleName )
- }
-
- def getDefinitionName (_type:JavaType) : String = {
- val baseName = if (config.useTypeIdForDefinitionName) _type.getRawClass.getTypeName else extractTypeName(_type)
-
- if (_type.hasGenericTypes) {
- val containedTypes = Range(0, _type.containedTypeCount()).map(_type.containedType)
- val typeNames = containedTypes.map(getDefinitionName).mkString(",")
- s"$baseName($typeNames)"
- } else {
- baseName
- }
- }
-
- // Either creates new definitions or return $ref to existing one
- def getOrCreateDefinition(_type:JavaType)(objectDefinitionBuilder:(ObjectNode) => Option[JsonObjectFormatVisitor]):DefinitionInfo = {
-
- class2Ref.get(_type) match {
- case Some(ref) =>
-
- workInProgress match {
- case None =>
- DefinitionInfo(Some(ref), None)
-
- case Some(w) =>
- // this is a recursive polymorphism call
- if ( _type != w.typeInProgress) throw new Exception(s"Wrong type - working on ${w.typeInProgress} - got ${_type}")
-
- DefinitionInfo(None, objectDefinitionBuilder(w.nodeInProgress))
- }
-
- case None =>
-
- // new one - must build it
- var retryCount = 0
- val definitionName = getDefinitionName(_type)
- var shortRef = definitionName
- var longRef = "#/definitions/" + definitionName
- while( class2Ref.values.toList.contains(longRef)) {
- retryCount = retryCount + 1
- shortRef = definitionName + "_" + retryCount
- longRef = "#/definitions/" + definitionName + "_" + retryCount
- }
- class2Ref = class2Ref + (_type -> longRef)
-
- // create definition
- val node = JsonNodeFactory.instance.objectNode()
-
- // When processing polymorphism, we might get multiple recursive calls to getOrCreateDefinition - this is a wau to combine them
- workInProgress = Some(WorkInProgress(_type, node))
-
- definitionsNode.set(shortRef, node)
-
- val jsonObjectFormatVisitor = objectDefinitionBuilder.apply(node)
-
- workInProgress = None
-
- DefinitionInfo(Some(longRef), jsonObjectFormatVisitor)
- }
- }
-
- def getFinalDefinitionsNode():Option[ObjectNode] = {
- if (class2Ref.isEmpty) None else Some(definitionsNode)
- }
-
- }
-
- class MyJsonFormatVisitorWrapper
- (
- objectMapper: ObjectMapper,
- level:Int = 0,
- val node: ObjectNode = JsonNodeFactory.instance.objectNode(),
- val definitionsHandler:DefinitionsHandler,
- currentProperty:Option[BeanProperty] // This property may represent the BeanProperty when we're directly processing beneath the property
- ) extends JsonFormatVisitorWrapper with MySerializerProvider {
-
- def l(s: => String): Unit = {
- if (!debug) return
-
- var indent = ""
- for(_ <- 0 until level) {
- indent = indent + " "
- }
- println(indent + s)
- }
-
- def createChild(childNode: ObjectNode, currentProperty:Option[BeanProperty]): MyJsonFormatVisitorWrapper = {
- new MyJsonFormatVisitorWrapper(objectMapper, level + 1, node = childNode, definitionsHandler = definitionsHandler, currentProperty = currentProperty)
- }
-
- def extractDefaultValue(p: BeanProperty): Option[String] = {
- // Prefer default-value from @JsonProperty
- selectAnnotation(p, classOf[JsonProperty]).flatMap {
- jsonProp =>
- val defaultValue = jsonProp.defaultValue();
- // Since it is default set to "", we should only use it if it is nonEmpty
- if (defaultValue.nonEmpty) {
- Some(defaultValue)
- } else None
- }.orElse {
- // Then, look for @JsonSchemaDefault
- selectAnnotation(p, classOf[JsonSchemaDefault]).map {
- defaultValue =>
- defaultValue.value()
- }
- }
- }
-
- override def expectStringFormat(_type: JavaType) = {
- l(s"expectStringFormat - _type: ${_type}")
-
- node.put("type", "string")
-
- // Check if we should include minLength and/or maxLength
- case class MinAndMaxLength(minLength:Option[Int], maxLength:Option[Int])
-
- // If we have 'currentProperty', then check for annotations and insert stuff into schema.
- currentProperty.flatMap {
- p =>
-
- // Look for @NotBlank
- selectAnnotation(p, classOf[NotBlank]).map {
- _ =>
- // Need to write this pattern first in case we should override it with more specific @Pattern
- node.put("pattern", "^.*\\S+.*$")
- }
-
- // Look for @Pattern
- selectAnnotation(p, classOf[Pattern]).map {
- pattern =>
- node.put("pattern", pattern.regexp())
- }
-
- // Look for @Pattern.List
- selectAnnotation(p, classOf[Pattern.List]).map {
- patterns => {
- val regex = patterns.value().map(_.regexp).foldLeft("^")(_ + "(?=" + _ + ")").concat(".*$")
- node.put("pattern", regex)
- }
- }
-
- extractDefaultValue(p).map { value =>
- node.put("default", value)
- }
-
- // Look for @JsonSchemaExamples
- selectAnnotation(p, classOf[JsonSchemaExamples]).map {
- exampleValues =>
- val examples: ArrayNode = JsonNodeFactory.instance.arrayNode()
- exampleValues.value().map {
- exampleValue => examples.add(exampleValue)
- }
- node.set("examples", examples)
- ()
- }
-
- // Look for @Email
- selectAnnotation(p, classOf[Email]).map {
- _ =>
- node.put("format", "email")
- }
-
- // Look for a @Size annotation, which should have a set of min/max properties.
- val minAndMaxLength:Option[MinAndMaxLength] = selectAnnotation(p, classOf[Size])
- .map {
- size =>
- (size.min(), size.max()) match {
- case (0, max) => MinAndMaxLength(None, Some(max))
- case (min, Integer.MAX_VALUE) => MinAndMaxLength(Some(min), None)
- case (min, max) => MinAndMaxLength(Some(min), Some(max))
- }
- }
- // Look for other annotations that don't have an explicit size, but we can infer the need to set a size for.
- .orElse {
- // If we're annotated with @NotNull, check to see if our config requires a size property to be generated.
- if (config.useMinLengthForNotNull && (selectAnnotation(p, classOf[NotNull]).isDefined)) {
- Option(MinAndMaxLength(Some(1), None))
- }
- // Other javax.validation annotations that require a length.
- else if (selectAnnotation(p, classOf[NotBlank]).isDefined || selectAnnotation(p, classOf[NotEmpty]).isDefined) {
- Option(MinAndMaxLength(Some(1), None))
- }
- // No length required.
- else {
- None
- }
- }
-
- // Apply size-data if found
- minAndMaxLength.map {
- minAndMax:MinAndMaxLength =>
- minAndMax.minLength.map( length => node.put("minLength", length) )
- minAndMax.maxLength.map( length => node.put("maxLength", length) )
- }
- }
-
- new JsonStringFormatVisitor with EnumSupport {
- val _node = node
- override def format(format: JsonValueFormat): Unit = {
- setFormat(node, format.toString)
- }
- }
-
- }
-
- override def expectArrayFormat(_type: JavaType) = {
- l(s"expectArrayFormat - _type: ${_type}")
-
- node.put("type", "array")
-
- if (config.uniqueItemClasses.exists( c => _type.getRawClass.isAssignableFrom(c))) {
- // Adding '"uniqueItems": true' to be used with https://github.com/jdorn/json-editor
- node.put("uniqueItems", true)
- setFormat(node, "checkbox")
- } else {
- // Try to set default format
- config.defaultArrayFormat.foreach {
- format => setFormat(node, format)
- }
- }
-
- currentProperty.map {
- p =>
- // Look for @Size
- selectAnnotation(p, classOf[Size]).map {
- size =>
- node.put("minItems", size.min())
- node.put("maxItems", size.max())
- }
-
- // Look for @NotEmpty
- selectAnnotation(p, classOf[NotEmpty]).map {
- notEmpty =>
- node.put("minItems", 1)
- }
- }
-
-
- val itemsNode = JsonNodeFactory.instance.objectNode()
- node.set("items", itemsNode)
-
- // We get improved result while processing scala-collections by getting elementType this way
- // instead of using the one which we receive in JsonArrayFormatVisitor.itemsFormat
- // This approach also works for Java
- val preferredElementType:JavaType = _type.getContentType
-
- new JsonArrayFormatVisitor with MySerializerProvider {
- override def itemsFormat(handler: JsonFormatVisitable, _elementType: JavaType): Unit = {
- l(s"expectArrayFormat - handler: $handler - elementType: ${_elementType} - preferredElementType: $preferredElementType")
- objectMapper.acceptJsonFormatVisitor(tryToReMapType(preferredElementType), createChild(itemsNode, currentProperty = None))
- }
-
- override def itemsFormat(format: JsonFormatTypes): Unit = {
- l(s"itemsFormat - format: $format")
- itemsNode.put("type", format.value())
- }
- }
- }
-
- override def expectNumberFormat(_type: JavaType) = {
- l("expectNumberFormat")
-
- node.put("type", "number")
-
- // Look for @Min, @Max, @DecimalMin, @DecimalMax => minimum, maximum
- currentProperty.map {
- p =>
- selectAnnotation(p, classOf[Min]).map {
- min =>
- node.put("minimum", min.value())
- }
-
- selectAnnotation(p, classOf[Max]).map {
- max =>
- node.put("maximum", max.value())
- }
-
- selectAnnotation(p, classOf[DecimalMin]).map {
- decimalMin =>
- node.put("minimum", decimalMin.value().toDouble)
- }
-
- selectAnnotation(p, classOf[DecimalMax]).map {
- decimalMax =>
- node.put("maximum", decimalMax.value().toDouble)
- }
-
- extractDefaultValue(p).map { value =>
- node.put("default", value.toInt)
- }
-
- // Look for @JsonSchemaExamples
- Option(p.getAnnotation(classOf[JsonSchemaExamples])).map {
- exampleValues =>
- val examples: ArrayNode = JsonNodeFactory.instance.arrayNode()
- exampleValues.value().map {
- exampleValue => examples.add(exampleValue)
- }
- node.set("examples", examples)
- }
- }
-
- new JsonNumberFormatVisitor with EnumSupport {
- val _node = node
- override def numberType(_type: NumberType): Unit = l(s"JsonNumberFormatVisitor.numberType: ${_type}")
- override def format(format: JsonValueFormat): Unit = {
- setFormat(node, format.toString)
- }
- }
- }
-
- override def expectAnyFormat(_type: JavaType) = {
- if (!config.disableWarnings) {
- log.warn(s"Not able to generate jsonSchema-info for type: ${_type} - probably using custom serializer which does not override acceptJsonFormatVisitor")
- }
-
-
- new JsonAnyFormatVisitor {
- }
-
- }
-
- override def expectIntegerFormat(_type: JavaType) = {
- l("expectIntegerFormat")
-
- node.put("type", "integer")
-
- // Look for @Min, @Max => minimum, maximum
- currentProperty.map {
- p =>
- selectAnnotation(p, classOf[Min]).map {
- min =>
- node.put("minimum", min.value())
- }
-
- selectAnnotation(p, classOf[Max]).map {
- max =>
- node.put("maximum", max.value())
- }
-
- extractDefaultValue(p).map { value =>
- node.put("default", value.toInt)
- }
-
- // Look for @JsonSchemaExamples
- selectAnnotation(p, classOf[JsonSchemaExamples]).map {
- exampleValues =>
- val examples: ArrayNode = JsonNodeFactory.instance.arrayNode()
- exampleValues.value().map {
- exampleValue => examples.add(exampleValue)
- }
- node.set("examples", examples)
- ()
- }
- }
-
-
- new JsonIntegerFormatVisitor with EnumSupport {
- val _node = node
- override def numberType(_type: NumberType): Unit = l(s"JsonIntegerFormatVisitor.numberType: ${_type}")
- override def format(format: JsonValueFormat): Unit = {
- setFormat(node, format.toString)
- }
- }
- }
-
- override def expectNullFormat(_type: JavaType) = {
- l(s"expectNullFormat - _type: ${_type}")
- node.put("type", "null")
- new JsonNullFormatVisitor {}
- }
-
-
- override def expectBooleanFormat(_type: JavaType) = {
- l("expectBooleanFormat")
-
- node.put("type", "boolean")
-
- currentProperty.map {
- p =>
- extractDefaultValue(p).map { value =>
- node.put("default", value.toBoolean)
- }
- }
-
- new JsonBooleanFormatVisitor with EnumSupport {
- val _node = node
- override def format(format: JsonValueFormat): Unit = {
- setFormat(node, format.toString)
- }
- }
- }
-
- override def expectMapFormat(_type: JavaType) = {
- l(s"expectMapFormat - _type: ${_type}")
-
- // There is no way to specify map in jsonSchema,
- // So we're going to treat it as type=object with additionalProperties = true,
- // so that it can hold whatever the map can hold
-
-
- node.put("type", "object")
-
- val additionalPropsObject = JsonNodeFactory.instance.objectNode()
- node.set("additionalProperties", additionalPropsObject)
-
- // If we're annotated with @NotEmpty, make sure we add a minItems of 1 to our schema here.
- currentProperty.map { p =>
- Option(p.getAnnotation(classOf[NotEmpty])).map {
- notEmpty =>
- node.put("minProperties", 1)
- }
- }
-
- definitionsHandler.pushWorkInProgress()
-
- val childVisitor = createChild(additionalPropsObject, None)
- objectMapper.acceptJsonFormatVisitor(tryToReMapType(_type.getContentType), childVisitor)
- definitionsHandler.popworkInProgress()
-
-
- new JsonMapFormatVisitor with MySerializerProvider {
- override def keyFormat(handler: JsonFormatVisitable, keyType: JavaType): Unit = {
- l(s"JsonMapFormatVisitor.keyFormat handler: $handler - keyType: $keyType")
- }
-
- override def valueFormat(handler: JsonFormatVisitable, valueType: JavaType): Unit = {
- l(s"JsonMapFormatVisitor.valueFormat handler: $handler - valueType: $valueType")
- }
- }
- }
-
-
- private def getRequiredArrayNode(objectNode:ObjectNode):ArrayNode = {
- Option(objectNode.get("required")).map(_.asInstanceOf[ArrayNode]).getOrElse {
- val rn = JsonNodeFactory.instance.arrayNode()
- objectNode.set("required", rn)
- rn
- }
- }
-
- private def getOptionsNode(objectNode:ObjectNode):ObjectNode = {
- getOrCreateObjectChild(objectNode, "options")
- }
-
- private def getOrCreateObjectChild(parentObjectNode:ObjectNode, name: String):ObjectNode = {
- Option(parentObjectNode.get(name)).map(_.asInstanceOf[ObjectNode]).getOrElse {
- val o = JsonNodeFactory.instance.objectNode()
- parentObjectNode.set(name, o)
- o
- }
- }
-
- case class PolymorphismInfo(typePropertyName:String, subTypeName:String)
-
- private def extractPolymorphismInfo(_type:JavaType):Option[PolymorphismInfo] = {
- val maybeBaseType = ClassUtil.findSuperTypes(_type, null, false).asScala.find { cl =>
- cl.getRawClass.isAnnotationPresent(classOf[JsonTypeInfo] )
- } orElse Option(_type.getSuperClass)
-
- maybeBaseType.flatMap { baseType =>
- val serializerOrNull = objectMapper
- .getSerializerFactory
- .createTypeSerializer(objectMapper.getSerializationConfig, baseType)
-
- Option(serializerOrNull).map { serializer =>
- serializer.getTypeInclusion match {
- case JsonTypeInfo.As.PROPERTY | JsonTypeInfo.As.EXISTING_PROPERTY =>
- val idResolver = serializer.getTypeIdResolver
- val id = idResolver match {
- // use custom implementation instead, because default implementation needs instance and we don't have one
- case _ : MinimalClassNameIdResolver => extractMinimalClassnameId(baseType, _type)
- case _ => idResolver.idFromValueAndType(null, _type.getRawClass)
- }
- PolymorphismInfo(serializer.getPropertyName, id)
-
- case x => throw new Exception(s"We do not support polymorphism using jsonTypeInfo.include() = $x")
- }
- }
- }
- }
-
- private def extractSubTypes(_type: JavaType):List[Class[_]] = {
-
- val ac = AnnotatedClassResolver.resolve(objectMapper.getDeserializationConfig, _type, objectMapper.getDeserializationConfig)
-
- Option(ac.getAnnotation(classOf[JsonTypeInfo])).map {
- jsonTypeInfo: JsonTypeInfo =>
-
- jsonTypeInfo.use() match {
- case JsonTypeInfo.Id.NAME =>
- // First we try to resolve types via manually finding annotations (if success, it will preserve the order), if not we fallback to use collectAndResolveSubtypesByClass()
- val subTypes: List[Class[_]] = Option(_type.getRawClass.getDeclaredAnnotation(classOf[JsonSubTypes])).map {
- ann: JsonSubTypes =>
- // We found it via @JsonSubTypes-annotation
- ann.value().map {
- t: JsonSubTypes.Type => t.value()
- }.toList
- }.getOrElse {
- // We did not find it via @JsonSubTypes-annotation (Probably since it is using mixin's) => Must fallback to using collectAndResolveSubtypesByClass
- val resolvedSubTypes = objectMapper.getSubtypeResolver.collectAndResolveSubtypesByClass(objectMapper.getDeserializationConfig, ac).asScala.toList
- resolvedSubTypes.map( _.getType)
- .filter( c => _type.getRawClass.isAssignableFrom(c) && _type.getRawClass != c)
- }
-
- subTypes
-
- case _ =>
- // Just find all subclasses
- config.subclassesResolver.getSubclasses(_type.getRawClass)
- }
-
- }.getOrElse(List())
- }
-
- def tryToReMapType(originalClass: Class[_]):Class[_] = {
- config.classTypeReMapping.get(originalClass).map {
- mappedToClass:Class[_] =>
- l(s"Class $originalClass is remapped to $mappedToClass")
- mappedToClass
- }.getOrElse(originalClass)
- }
-
- private def tryToReMapType(originalType: JavaType):JavaType = {
- val _type:JavaType = config.classTypeReMapping.get(originalType.getRawClass).map {
- mappedToClass:Class[_] =>
- l(s"Class ${originalType.getRawClass} is remapped to $mappedToClass")
- val mappedToJavaType:JavaType = objectMapper.getTypeFactory.constructType(mappedToClass)
- mappedToJavaType
- }.getOrElse(originalType)
-
- _type
- }
-
- // Returns the value of merge
- private def injectFromJsonSchemaInject(a:JsonSchemaInject, thisObjectNode:ObjectNode): Boolean ={
- // Must parse json
- val injectJsonNode = objectMapper.readTree(a.json())
- Option(a.jsonSupplier())
- .flatMap(cls => Option(cls.newInstance().get()))
- .foreach(json => merge(injectJsonNode, json))
- if (a.jsonSupplierViaLookup().nonEmpty) {
- val json = config.jsonSuppliers.get(a.jsonSupplierViaLookup()).getOrElse(throw new Exception(s"@JsonSchemaInject(jsonSupplierLookup='${a.jsonSupplierViaLookup()}') does not exist in config.jsonSupplierLookup-map")).get()
- merge(injectJsonNode, json)
- }
- a.strings().foreach(v => injectJsonNode.visit(v.path(), (o, n) => o.put(n, v.value())))
- a.ints().foreach(v => injectJsonNode.visit(v.path(), (o, n) => o.put(n, v.value())))
- a.bools().foreach(v => injectJsonNode.visit(v.path(), (o, n) => o.put(n, v.value())))
-
- val mergeInjectedJson: Boolean = a.merge()
- if ( !mergeInjectedJson) {
- // Since we're not merging, we must remove all content of thisObjectNode before injecting.
- // We cannot just "replace" it with injectJsonNode, since thisObjectNode already have been added to its parent
- thisObjectNode.removeAll()
- }
-
- merge(thisObjectNode, injectJsonNode)
-
- // return
- mergeInjectedJson
- }
-
- override def expectObjectFormat(_type: JavaType) = {
-
- val subTypes: List[Class[_]] = extractSubTypes(_type)
-
- // Check if we have subtypes
- if (subTypes.nonEmpty) {
- // We have subtypes
- //l(s"polymorphism - subTypes: $subTypes")
-
- val anyOfArrayNode = JsonNodeFactory.instance.arrayNode()
- node.set("oneOf", anyOfArrayNode)
-
- subTypes.foreach {
- subType: Class[_] =>
- l(s"polymorphism - subType: $subType")
- val definitionInfo: DefinitionInfo = definitionsHandler.getOrCreateDefinition(objectMapper.constructType(subType)){
- objectNode =>
-
- val childVisitor = createChild(objectNode, currentProperty = None)
- objectMapper.acceptJsonFormatVisitor(tryToReMapType(subType), childVisitor)
-
- None
- }
-
- val thisOneOfNode = JsonNodeFactory.instance.objectNode()
- thisOneOfNode.put("$ref", definitionInfo.ref.get)
-
- // If class is annotated with JsonSchemaTitle, we should add it
- Option(subType.getDeclaredAnnotation(classOf[JsonSchemaTitle])).map(_.value()).foreach {
- title =>
- thisOneOfNode.put("title", title)
- }
-
- anyOfArrayNode.add(thisOneOfNode)
-
- }
-
- null // Returning null to stop jackson from visiting this object since we have done it manually
-
- } else {
- // We do not have subtypes
-
- val objectBuilder:ObjectNode => Option[JsonObjectFormatVisitor] = {
- thisObjectNode:ObjectNode =>
-
- thisObjectNode.put("type", "object")
- thisObjectNode.put("additionalProperties", !config.failOnUnknownProperties)
-
- // If class is annotated with JsonSchemaFormat, we should add it
- val ac = AnnotatedClassResolver.resolve(objectMapper.getDeserializationConfig, _type, objectMapper.getDeserializationConfig)
- resolvePropertyFormat(_type, objectMapper).foreach {
- format =>
- setFormat(thisObjectNode, format)
- }
-
- // If class is annotated with JsonSchemaDescription, we should add it
- Option(ac.getAnnotations.get(classOf[JsonSchemaDescription])).map(_.value())
- .orElse(Option(ac.getAnnotations.get(classOf[JsonPropertyDescription])).map(_.value))
- .foreach {
- description: String =>
- thisObjectNode.put("description", description)
- }
-
- // If class is annotated with JsonSchemaTitle, we should add it
- Option(ac.getAnnotations.get(classOf[JsonSchemaTitle])).map(_.value()).foreach {
- title =>
- thisObjectNode.put("title", title)
- }
-
- // If class is annotated with JsonSchemaOptions, we should add it
- Option(ac.getAnnotations.get(classOf[JsonSchemaOptions])).map(_.items()).foreach {
- items =>
- val optionsNode = getOptionsNode(thisObjectNode)
- items.foreach {
- item =>
- optionsNode.put(item.name, item.value)
- }
- }
-
- // Optionally add JsonSchemaInject to top-level
- val renderProps:Boolean = selectAnnotation(ac, classOf[JsonSchemaInject]).map {
- a =>
- val merged = injectFromJsonSchemaInject(a, thisObjectNode)
- merged == true // Continue to render props since we merged injection
- }.getOrElse( true ) // nothing injected => of course we should render props
-
- if (renderProps) {
-
- val propertiesNode = getOrCreateObjectChild(thisObjectNode, "properties")
-
- extractPolymorphismInfo(_type).map {
- case pi: PolymorphismInfo =>
- // This class is a child in a polymorphism config..
- // Set the title = subTypeName
- thisObjectNode.put("title", pi.subTypeName)
-
- // must inject the 'type'-param and value as enum with only one possible value
- // This is done to make sure the json generated from the schema using this oneOf
- // contains the correct "type info"
- val enumValuesNode = JsonNodeFactory.instance.arrayNode()
- enumValuesNode.add(pi.subTypeName)
-
- val enumObjectNode = getOrCreateObjectChild(propertiesNode, pi.typePropertyName)
- enumObjectNode.put("type", "string")
- enumObjectNode.set("enum", enumValuesNode)
- enumObjectNode.put("default", pi.subTypeName)
-
- if (config.hidePolymorphismTypeProperty) {
- // Make sure the editor hides this polymorphism-specific property
- val optionsNode = JsonNodeFactory.instance.objectNode()
- enumObjectNode.set("options", optionsNode)
- optionsNode.put("hidden", true)
- }
-
- getRequiredArrayNode(thisObjectNode).add(pi.typePropertyName)
-
- if (config.useMultipleEditorSelectViaProperty) {
- // https://github.com/jdorn/json-editor/issues/709
- // Generate info to help generated editor to select correct oneOf-type
- // when populating the gui/schema with existing data
- val objectOptionsNode = getOrCreateObjectChild( thisObjectNode, "options")
- val multipleEditorSelectViaPropertyNode = getOrCreateObjectChild( objectOptionsNode, "multiple_editor_select_via_property")
- multipleEditorSelectViaPropertyNode.put("property", pi.typePropertyName)
- multipleEditorSelectViaPropertyNode.put("value", pi.subTypeName)
- ()
- }
-
- }
-
- Some(new JsonObjectFormatVisitor with MySerializerProvider {
-
-
- // Used when rendering schema using propertyOrdering as specified here:
- // https://github.com/jdorn/json-editor#property-ordering
- var nextPropertyOrderIndex = 1
-
- def myPropertyHandler(propertyName: String, propertyType: JavaType, prop: Option[BeanProperty], jsonPropertyRequired: Boolean): Unit = {
- l(s"JsonObjectFormatVisitor - ${propertyName}: ${propertyType}")
-
- if (propertiesNode.get(propertyName) != null) {
- if (!config.disableWarnings) {
- log.warn(s"Ignoring property '$propertyName' in $propertyType since it has already been added, probably as type-property using polymorphism")
- }
- return
- }
-
- // Need to check for Option/Optional-special-case before we know what node to use here.
- case class PropertyNode(main: ObjectNode, meta: ObjectNode)
-
- // Check if we should set this property as required. Primitive types MUST have a value, as does anything
- // with a @JsonProperty that has "required" set to true. Lastly, various javax.validation annotations also
- // make this required.
- val requiredProperty: Boolean = if (propertyType.getRawClass.isPrimitive || jsonPropertyRequired || validationAnnotationRequired(prop)) {
- true
- } else {
- false
- }
-
- val thisPropertyNode: PropertyNode = {
- val thisPropertyNode = JsonNodeFactory.instance.objectNode()
- propertiesNode.set(propertyName, thisPropertyNode)
-
- if (config.usePropertyOrdering) {
- thisPropertyNode.put("propertyOrder", nextPropertyOrderIndex)
- nextPropertyOrderIndex = nextPropertyOrderIndex + 1
- }
-
- // Figure out if the type is considered optional by either Java or Scala.
- val optionalType: Boolean = classOf[Option[_]].isAssignableFrom(propertyType.getRawClass) ||
- classOf[Optional[_]].isAssignableFrom(propertyType.getRawClass)
-
- // If the property is not required, and our configuration allows it, let's go ahead and mark the type as nullable.
- if (!requiredProperty && ((config.useOneOfForOption && optionalType) ||
- (config.useOneOfForNullables && !optionalType))) {
- // We support this type being null, insert a oneOf consisting of a sentinel "null" and the real type.
- val oneOfArray = JsonNodeFactory.instance.arrayNode()
- thisPropertyNode.set("oneOf", oneOfArray)
-
- // Create our sentinel "null" value for the case no value is provided.
- val oneOfNull = JsonNodeFactory.instance.objectNode()
- oneOfNull.put("type", "null")
- oneOfNull.put("title", "Not included")
- oneOfArray.add(oneOfNull)
-
- // If our nullable/optional type has a value, it'll be this.
- val oneOfReal = JsonNodeFactory.instance.objectNode()
- oneOfArray.add(oneOfReal)
-
- // Return oneOfReal which, from now on, will be used as the node representing this property
- PropertyNode(oneOfReal, thisPropertyNode)
- } else {
- // Our type must not be null: primitives, @NotNull annotations, @JsonProperty annotations marked required etc.
- PropertyNode(thisPropertyNode, thisPropertyNode)
- }
- }
-
- // Continue processing this property
- val childVisitor = createChild(thisPropertyNode.main, currentProperty = prop)
-
-
- // Push current work in progress since we're about to start working on a new class
- definitionsHandler.pushWorkInProgress()
-
- if ((classOf[Option[_]].isAssignableFrom(propertyType.getRawClass) || classOf[Optional[_]].isAssignableFrom(propertyType.getRawClass)) && propertyType.containedTypeCount() >= 1) {
-
- // Property is scala Option or Java Optional.
- //
- // Due to Java's Type Erasure, the type behind Option is lost.
- // To workaround this, we use the same workaround as jackson-scala-module described here:
- // https://github.com/FasterXML/jackson-module-scala/wiki/FAQ#deserializing-optionint-and-other-primitive-challenges
-
- val optionType: JavaType = resolveType(propertyType, prop, objectMapper)
-
- objectMapper.acceptJsonFormatVisitor(tryToReMapType(optionType), childVisitor)
-
- } else {
- objectMapper.acceptJsonFormatVisitor(tryToReMapType(propertyType), childVisitor)
- }
-
- // Pop back the work we were working on..
- definitionsHandler.popworkInProgress()
-
- prop.flatMap(resolvePropertyFormat(_)).foreach {
- format =>
- setFormat(thisPropertyNode.main, format)
- }
-
- // Optionally add description
- prop.flatMap {
- p: BeanProperty =>
- Option(p.getAnnotation(classOf[JsonSchemaDescription])).map(_.value())
- .orElse(Option(p.getAnnotation(classOf[JsonPropertyDescription])).map(_.value()))
- }.map {
- description =>
- thisPropertyNode.meta.put("description", description)
- }
-
- // If this property is required, add it to our array of required properties.
- if (requiredProperty) {
- getRequiredArrayNode(thisObjectNode).add(propertyName)
- }
-
- // Optionally add title
- prop.flatMap {
- p: BeanProperty =>
- Option(p.getAnnotation(classOf[JsonSchemaTitle]))
- }.map(_.value())
- .orElse {
- if (config.autoGenerateTitleForProperties) {
- // We should generate 'pretty-name' based on propertyName
- Some(generateTitleFromPropertyName(propertyName))
- } else None
- }
- .map {
- title =>
- thisPropertyNode.meta.put("title", title)
- }
-
- // Optionally add options
- prop.flatMap {
- p: BeanProperty =>
- Option(p.getAnnotation(classOf[JsonSchemaOptions]))
- }.map(_.items()).foreach {
- items =>
- val optionsNode = getOptionsNode(thisPropertyNode.meta)
- items.foreach {
- item =>
- optionsNode.put(item.name, item.value)
-
- }
- }
-
- // Optionally add JsonSchemaInject
- prop.flatMap {
- p: BeanProperty =>
- selectAnnotation(p, classOf[JsonSchemaInject]) match {
- case Some(a) => Some(a)
- case None =>
- // Try to look at the class itself -- Looks like this is the only way to find it if the type is Enum
- Option(p.getType.getRawClass.getAnnotation(classOf[JsonSchemaInject]))
- .filter( annotationIsApplicable(_) )
- }
- }.foreach {
- a =>
- injectFromJsonSchemaInject(a, thisPropertyNode.meta)
- }
- }
-
- override def optionalProperty(prop: BeanProperty): Unit = {
- l(s"JsonObjectFormatVisitor.optionalProperty: prop:${prop}")
- myPropertyHandler(prop.getName, prop.getType, Some(prop), jsonPropertyRequired = false)
- }
-
- override def optionalProperty(name: String, handler: JsonFormatVisitable, propertyTypeHint: JavaType): Unit = {
- l(s"JsonObjectFormatVisitor.optionalProperty: name:${name} handler:${handler} propertyTypeHint:${propertyTypeHint}")
- myPropertyHandler(name, propertyTypeHint, None, jsonPropertyRequired = false)
- }
-
- override def property(prop: BeanProperty): Unit = {
- l(s"JsonObjectFormatVisitor.property: prop:${prop}")
- myPropertyHandler(prop.getName, prop.getType, Some(prop), jsonPropertyRequired = true)
- }
-
- override def property(name: String, handler: JsonFormatVisitable, propertyTypeHint: JavaType): Unit = {
- l(s"JsonObjectFormatVisitor.property: name:${name} handler:${handler} propertyTypeHint:${propertyTypeHint}")
- myPropertyHandler(name, propertyTypeHint, None, jsonPropertyRequired = true)
- }
-
- // Checks to see if a javax.validation field that makes our field required is present.
- private def validationAnnotationRequired(prop: Option[BeanProperty]): Boolean = {
- prop.exists(p => selectAnnotation(p, classOf[NotNull]).isDefined || selectAnnotation(p, classOf[NotBlank]).isDefined || selectAnnotation(p, classOf[NotEmpty]).isDefined)
- }
- })
- } else None
- }
-
- if ( level == 0) {
- // This is the first level - we must not use definitions
- objectBuilder(node).orNull
- } else {
- val definitionInfo: DefinitionInfo = definitionsHandler.getOrCreateDefinition(_type)(objectBuilder)
-
- definitionInfo.ref.foreach {
- r =>
- // Must add ref to def at "this location"
- node.put("$ref", r)
- }
-
- definitionInfo.jsonObjectFormatVisitor.orNull
- }
-
- }
-
- }
-
- }
-
- private def extractMinimalClassnameId(baseType: JavaType, child: JavaType) = {
- // code taken straight from Jackson's MinimalClassNameIdResolver
- val base = baseType.getRawClass.getName
- val ix = base.lastIndexOf('.')
- val _basePackagePrefix = if (ix < 0) { // can this ever occur?
- "."
- } else {
- base.substring(0, ix + 1)
- }
- val n = child.getRawClass.getName
- if (n.startsWith(_basePackagePrefix)) { // note: we will leave the leading dot in there
- n.substring(_basePackagePrefix.length - 1)
- } else {
- n
- }
- }
-
- private def merge(mainNode:JsonNode, updateNode:JsonNode):Unit = {
- val fieldNames = updateNode.fieldNames()
- while (fieldNames.hasNext) {
-
- val fieldName = fieldNames.next()
- val jsonNode = mainNode.get(fieldName)
- // if field exists and is an embedded object
- if (jsonNode != null && jsonNode.isObject) {
- merge(jsonNode, updateNode.get(fieldName))
- }
- else {
- mainNode match {
- case node: ObjectNode =>
- // Overwrite field
- val value = updateNode.get(fieldName)
- node.set(fieldName, value)
- ()
- case _ =>
- }
- }
-
- }
- }
-
- def generateTitleFromPropertyName(propertyName:String):String = {
- // Code found here: http://stackoverflow.com/questions/2559759/how-do-i-convert-camelcase-into-human-readable-names-in-java
- val s = propertyName.replaceAll(
- String.format("%s|%s|%s",
- "(?<=[A-Z])(?=[A-Z][a-z])",
- "(?<=[^A-Z])(?=[A-Z])",
- "(?<=[A-Za-z])(?=[^A-Za-z])"
- ),
- " "
- )
-
- // Make the first letter uppercase
- s.substring(0,1).toUpperCase() + s.substring(1)
- }
-
- def resolvePropertyFormat(_type: JavaType, objectMapper:ObjectMapper):Option[String] = {
- val ac = AnnotatedClassResolver.resolve(objectMapper.getDeserializationConfig, _type, objectMapper.getDeserializationConfig)
- resolvePropertyFormat(Option(ac.getAnnotation(classOf[JsonSchemaFormat])), _type.getRawClass.getName)
- }
-
- def resolvePropertyFormat(prop: BeanProperty):Option[String] = {
- // Prefer format specified in annotation
- resolvePropertyFormat(Option(prop.getAnnotation(classOf[JsonSchemaFormat])), prop.getType.getRawClass.getName)
- }
-
- def resolvePropertyFormat(jsonSchemaFormatAnnotation:Option[JsonSchemaFormat], rawClassName:String):Option[String] = {
- // Prefer format specified in annotation
- jsonSchemaFormatAnnotation.map {
- jsonSchemaFormat =>
- jsonSchemaFormat.value()
- }.orElse {
- config.customType2FormatMapping.get(rawClassName)
- }
- }
-
- def resolveType(propertyType:JavaType, prop: Option[BeanProperty], objectMapper: ObjectMapper):JavaType = {
- val containedType = propertyType.containedType(0)
-
- if ( containedType.getRawClass == classOf[Object] ) {
- // try to resolve it via @JsonDeserialize as described here: https://github.com/FasterXML/jackson-module-scala/wiki/FAQ#deserializing-optionint-and-other-primitive-challenges
- prop.flatMap {
- p:BeanProperty =>
- Option(p.getAnnotation(classOf[JsonDeserialize]))
- }.flatMap {
- jsonDeserialize:JsonDeserialize =>
- Option(jsonDeserialize.contentAs()).map {
- clazz =>
- objectMapper.getTypeFactory.constructType(clazz)
- }
- }.getOrElse( {
- if (!config.disableWarnings) {
- log.warn(s"$prop - Contained type is java.lang.Object and we're unable to extract its Type using fallback-approach looking for @JsonDeserialize")
- }
- containedType
- })
-
- } else {
- // use containedType as is
- containedType
- }
- }
-
- def generateJsonSchema[T <: Any](clazz: Class[T]): JsonNode = generateJsonSchema(clazz, None, None)
- def generateJsonSchema[T <: Any](javaType: JavaType): JsonNode = generateJsonSchema(javaType, None, None)
-
- // Java-API
- def generateJsonSchema[T <: Any](clazz: Class[T], title:String, description:String): JsonNode = generateJsonSchema(clazz, Option(title), Option(description))
- // Java-API
- def generateJsonSchema[T <: Any](javaType: JavaType, title:String, description:String): JsonNode = generateJsonSchema(javaType, Option(title), Option(description))
-
- def generateJsonSchema[T <: Any](clazz: Class[T], title:Option[String], description:Option[String]): JsonNode = {
-
-
- def tryToReMapType(originalClass: Class[_]):Class[_] = {
- config.classTypeReMapping.get(originalClass).map {
- mappedToClass:Class[_] =>
- if (debug) {
- println(s"Class $originalClass is remapped to $mappedToClass")
- }
- mappedToClass
- }.getOrElse(originalClass)
- }
-
- val clazzToUse = tryToReMapType(clazz)
-
- val javaType = rootObjectMapper.constructType(clazzToUse)
-
- generateJsonSchema(javaType, title, description)
-
- }
-
- def generateJsonSchema[T <: Any](javaType: JavaType, title:Option[String], description:Option[String]): JsonNode = {
-
- val rootNode = JsonNodeFactory.instance.objectNode()
-
- // Specify that this is a v4 json schema
- rootNode.put("$schema", config.jsonSchemaDraft.url)
- //rootNode.put("id", "http://my.site/myschema#")
-
- // Add schema title
- title.orElse {
- Some(generateTitleFromPropertyName(javaType.getRawClass.getSimpleName))
- }.flatMap {
- title =>
- // Skip it if specified to empty string
- if ( title.isEmpty) None else Some(title)
- }.map {
- title =>
- rootNode.put("title", title)
- // If root class is annotated with @JsonSchemaTitle, it will later override this title
- }
-
- // Maybe set schema description
- description.map {
- d =>
- rootNode.put("description", d)
- // If root class is annotated with @JsonSchemaDescription, it will later override this description
- }
-
-
- val definitionsHandler = new DefinitionsHandler
- val rootVisitor = new MyJsonFormatVisitorWrapper(rootObjectMapper, node = rootNode, definitionsHandler = definitionsHandler, currentProperty = None)
-
-
- rootObjectMapper.acceptJsonFormatVisitor(javaType, rootVisitor)
-
- definitionsHandler.getFinalDefinitionsNode().foreach {
- definitionsNode => rootNode.set("definitions", definitionsNode)
- ()
- }
-
- rootNode
-
- }
-
- implicit class JsonNodeExtension(o:JsonNode) {
- def visit(path: String, f: (ObjectNode, String) => Unit) = {
- var p = o
-
- val split = path.split('/')
- for (name <- split.dropRight(1)) {
- p = Option(p.get(name)).getOrElse(p.asInstanceOf[ObjectNode].putObject(name))
- }
- f(p.asInstanceOf[ObjectNode], split.last)
- }
- }
-}
diff --git a/src/test/java/com/kjetland/jackson/jsonSchema/JsonSchemaGeneratorTest.java b/src/test/java/com/kjetland/jackson/jsonSchema/JsonSchemaGeneratorTest.java
new file mode 100644
index 0000000..5b7eee3
--- /dev/null
+++ b/src/test/java/com/kjetland/jackson/jsonSchema/JsonSchemaGeneratorTest.java
@@ -0,0 +1,1316 @@
+package com.kjetland.jackson.jsonSchema;
+
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import com.fasterxml.jackson.datatype.joda.JodaModule;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import static com.kjetland.jackson.jsonSchema.TestUtils.*;
+import com.kjetland.jackson.jsonSchema.testData.BoringContainer;
+import com.kjetland.jackson.jsonSchema.testData.ClassNotExtendingAnything;
+import com.kjetland.jackson.jsonSchema.testData.ClassUsingValidationWithGroups;
+import com.kjetland.jackson.jsonSchema.testData.ClassUsingValidationWithGroups.ValidationGroup1;
+import com.kjetland.jackson.jsonSchema.testData.ClassUsingValidationWithGroups.ValidationGroup2;
+import com.kjetland.jackson.jsonSchema.testData.ClassUsingValidationWithGroups.ValidationGroup3_notInUse;
+import com.kjetland.jackson.jsonSchema.testData.MyEnum;
+import com.kjetland.jackson.jsonSchema.testData.PojoUsingJsonTypeName;
+import com.kjetland.jackson.jsonSchema.testData.PojoWithArrays;
+import com.kjetland.jackson.jsonSchema.testData.PojoWithCustomSerializer;
+import com.kjetland.jackson.jsonSchema.testData.PojoWithCustomSerializerDeserializer;
+import com.kjetland.jackson.jsonSchema.testData.PojoWithCustomSerializerSerializer;
+import com.kjetland.jackson.jsonSchema.testData.PojoWithParent;
+import com.kjetland.jackson.jsonSchema.testData.PolymorphismOrdering;
+import com.kjetland.jackson.jsonSchema.testData.TestData;
+import com.kjetland.jackson.jsonSchema.testData.UsingJsonSchemaInjectTop.*;
+import com.kjetland.jackson.jsonSchema.testData.generic.GenericClassContainer;
+import com.kjetland.jackson.jsonSchema.testData.mixin.MixinModule;
+import com.kjetland.jackson.jsonSchema.testData.mixin.MixinParent;
+import com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child1;
+import com.kjetland.jackson.jsonSchema.testData.polymorphism1.Parent;
+import com.kjetland.jackson.jsonSchema.testData.polymorphism2.Parent2;
+import com.kjetland.jackson.jsonSchema.testData.polymorphism3.Parent3;
+import com.kjetland.jackson.jsonSchema.testData.polymorphism4.Child41;
+import com.kjetland.jackson.jsonSchema.testData.polymorphism4.Child42;
+import com.kjetland.jackson.jsonSchema.testData.polymorphism5.Parent5;
+import com.kjetland.jackson.jsonSchema.testData.polymorphism6.Parent6;
+import com.kjetland.jackson.jsonSchema.testData_issue_24.EntityWrapper;
+import static java.lang.System.out;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.stream.Stream;
+import javax.validation.groups.Default;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.junit.jupiter.api.Test;
+
+/**
+ *
+ * @author alex
+ */
+public class JsonSchemaGeneratorTest {
+
+ ObjectMapper objectMapper = new ObjectMapper();
+ MixinModule mixinModule = new MixinModule();
+
+ {
+ var simpleModule = new SimpleModule();
+ simpleModule.addSerializer(PojoWithCustomSerializer.class, new PojoWithCustomSerializerSerializer());
+ simpleModule.addDeserializer(PojoWithCustomSerializer.class, new PojoWithCustomSerializerDeserializer());
+ objectMapper.registerModule(simpleModule);
+
+ objectMapper.registerModule(new JavaTimeModule());
+ objectMapper.registerModule(new Jdk8Module());
+ objectMapper.registerModule(new JodaModule());
+
+ // For the mixin-test
+ objectMapper.registerModule(mixinModule);
+
+ objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
+ objectMapper.setTimeZone(TimeZone.getDefault());
+ }
+
+ JsonSchemaGenerator jsonSchemaGenerator = new JsonSchemaGenerator(objectMapper);
+ JsonSchemaGenerator jsonSchemaGeneratorNullable = new JsonSchemaGenerator(objectMapper,
+ JsonSchemaConfig.NULLABLE);
+ JsonSchemaGenerator jsonSchemaGeneratorWithIds = new JsonSchemaGenerator(objectMapper,
+ JsonSchemaConfig.builder().useTypeIdForDefinitionName(true).build());
+ JsonSchemaGenerator jsonSchemaGeneratorWithIdsNullable = new JsonSchemaGenerator(objectMapper,
+ JsonSchemaConfig.NULLABLE.toBuilder().useTypeIdForDefinitionName(true).build());
+ JsonSchemaGenerator jsonSchemaGeneratorHTML5 = new JsonSchemaGenerator(objectMapper,
+ JsonSchemaConfig.JSON_EDITOR);
+ JsonSchemaGenerator jsonSchemaGeneratorHTML5Nullable = new JsonSchemaGenerator(objectMapper,
+ JsonSchemaConfig.JSON_EDITOR.toBuilder().useOneOfForNullables(true).build());
+ JsonSchemaGenerator jsonSchemaGenerator_draft_06 = new JsonSchemaGenerator(objectMapper,
+ JsonSchemaConfig.builder().jsonSchemaDraft(JsonSchemaDraft.DRAFT_06).build());
+ JsonSchemaGenerator jsonSchemaGenerator_draft_07 = new JsonSchemaGenerator(objectMapper,
+ JsonSchemaConfig.builder().jsonSchemaDraft(JsonSchemaDraft.DRAFT_07).build());
+ JsonSchemaGenerator jsonSchemaGenerator_draft_2019_09 = new JsonSchemaGenerator(objectMapper,
+ JsonSchemaConfig.builder().jsonSchemaDraft(JsonSchemaDraft.DRAFT_2019_09).build());
+
+ TestData testData = new TestData();
+
+
+ @Test void generateSchemaForPojo() {
+
+ var enumList = List.of(MyEnum.values()).stream().map(Object::toString).toList();
+
+ {
+ var jsonNode = assertToFromJson(jsonSchemaGenerator, testData.classNotExtendingAnything);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator, testData.classNotExtendingAnything.getClass(), jsonNode);
+
+ assertTrue (!schema.at("/additionalProperties").asBoolean());
+ assertEquals (schema.at("/properties/someString/type").asText(), "string");
+
+ assertEquals (schema.at("/properties/myEnum/type").asText(), "string");
+ assertEquals (getArrayNodeAsListOfStrings(schema.at("/properties/myEnum/enum")), enumList);
+ }
+
+ {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorNullable, testData.classNotExtendingAnything);
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorNullable, testData.classNotExtendingAnything.getClass(), jsonNode);
+
+ assertTrue (!schema.at("/additionalProperties").asBoolean());
+ assertNullableType(schema, "/properties/someString", "string");
+
+ assertNullableType(schema, "/properties/myEnum", "string");
+ assertEquals (getArrayNodeAsListOfStrings(schema.at("/properties/myEnum/oneOf/1/enum")), enumList);
+ }
+
+ {
+ var jsonNode = assertToFromJson(jsonSchemaGenerator, testData.genericClassVoid);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator, testData.genericClassVoid.getClass(), jsonNode);
+ assertEquals (schema.at("/type").asText(), "object");
+ assertTrue (!schema.at("/additionalProperties").asBoolean());
+ assertEquals (schema.at("/properties/content/type").asText(), "null");
+ assertEquals (schema.at("/properties/list/type").asText(), "array");
+ assertEquals (schema.at("/properties/list/items/type").asText(), "null");
+ }
+ {
+ var jsonNode = assertToFromJson(jsonSchemaGenerator, testData.genericMapLike);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator, testData.genericMapLike.getClass(), jsonNode);
+ assertEquals (schema.at("/type").asText(), "object");
+ assertEquals (schema.at("/additionalProperties/type").asText(), "string");
+ }
+ }
+
+ @Test void generatingSchemaForPojoWithJsonTypeInfo() {
+ var jsonNode = assertToFromJson(jsonSchemaGenerator, testData.child1);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator, testData.child1.getClass(), jsonNode);
+
+ assertTrue (!schema.at("/additionalProperties").asBoolean());
+ assertEquals (schema.at("/properties/parentString/type").asText(), "string");
+ assertJsonSubTypesInfo(schema, "type", "child1");
+ }
+
+ @Test void generateSchemaForPropertyWithJsonTypeInfo() {
+
+ // Java
+ {
+ var jsonNode = assertToFromJson(jsonSchemaGenerator, testData.pojoWithParent);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator, testData.pojoWithParent.getClass(), jsonNode);
+
+ assertTrue(!schema.at("/additionalProperties").asBoolean());
+ assertEquals(schema.at("/properties/pojoValue/type").asText(), "boolean");
+ assertDefaultValues(schema);
+
+ assertChild1(schema, "/properties/child/oneOf");
+ assertChild2(schema, "/properties/child/oneOf");
+ }
+
+ // Java - html5
+ {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorHTML5, testData.pojoWithParent);
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorHTML5, testData.pojoWithParent.getClass(), jsonNode);
+
+ assertTrue(!schema.at("/additionalProperties").asBoolean());
+ assertEquals(schema.at("/properties/pojoValue/type").asText(), "boolean");
+ assertDefaultValues(schema);
+
+ assertChild1(schema, "/properties/child/oneOf", true);
+ assertChild2(schema, "/properties/child/oneOf", true);
+ }
+
+ // Java - html5/nullable
+ {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorHTML5Nullable, testData.pojoWithParent);
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorHTML5Nullable, testData.pojoWithParent.getClass(), jsonNode);
+
+ assertTrue(!schema.at("/additionalProperties").asBoolean());
+ assertNullableType(schema, "/properties/pojoValue", "boolean");
+ assertNullableDefaultValues(schema);
+
+ assertNullableChild1(schema, "/properties/child/oneOf/1/oneOf", true);
+ assertNullableChild2(schema, "/properties/child/oneOf/1/oneOf", true);
+ }
+
+ //Using fully-qualified class names;
+ {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorWithIds, testData.pojoWithParent);
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorWithIds, testData.pojoWithParent.getClass(), jsonNode);
+
+ assertTrue(!schema.at("/additionalProperties").asBoolean());
+ assertEquals(schema.at("/properties/pojoValue/type").asText(), "boolean");
+ assertDefaultValues(schema);
+
+ assertChild1(schema, "/properties/child/oneOf", "com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child1");
+ assertChild2(schema, "/properties/child/oneOf", "com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child2");
+ }
+
+ // Using fully-qualified class names and nullable types
+ {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorWithIdsNullable, testData.pojoWithParent);
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorWithIdsNullable, testData.pojoWithParent.getClass(), jsonNode);
+
+ assertTrue(!schema.at("/additionalProperties").asBoolean());
+ assertNullableType(schema, "/properties/pojoValue", "boolean");
+ assertNullableDefaultValues(schema);
+
+ assertNullableChild1(schema, "/properties/child/oneOf/1/oneOf", "com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child1");
+ assertNullableChild2(schema, "/properties/child/oneOf/1/oneOf", "com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child2");
+ }
+ }
+
+
+ void assertDefaultValues(JsonNode schema) {
+ assertEquals (schema.at("/properties/stringWithDefault/type").asText(), "string");
+ assertEquals (schema.at("/properties/stringWithDefault/default").asText(), "x");
+ assertEquals (schema.at("/properties/intWithDefault/type").asText(), "integer");
+ assertEquals (schema.at("/properties/intWithDefault/default").asInt(), 12);
+ assertEquals (schema.at("/properties/booleanWithDefault/type").asText(), "boolean");
+ assertTrue (schema.at("/properties/booleanWithDefault/default").asBoolean());
+ };
+
+ void assertNullableDefaultValues(JsonNode schema) {
+ assertEquals (schema.at("/properties/stringWithDefault/oneOf/0/type").asText(), "null");
+ assertEquals (schema.at("/properties/stringWithDefault/oneOf/0/title").asText(), "Not included");
+ assertEquals (schema.at("/properties/stringWithDefault/oneOf/1/type").asText(), "string");
+ assertEquals (schema.at("/properties/stringWithDefault/oneOf/1/default").asText(), "x");
+
+ assertEquals (schema.at("/properties/intWithDefault/type").asText(), "integer");
+ assertEquals (schema.at("/properties/intWithDefault/default").asInt(), 12);
+ assertEquals (schema.at("/properties/booleanWithDefault/type").asText(), "boolean");
+ assertTrue (schema.at("/properties/booleanWithDefault/default").asBoolean());
+ };
+
+ void assertChild1(JsonNode node, String path) {
+ assertChild1(node, path, "Child1", "type", "child1", false);
+ }
+ void assertChild1(JsonNode node, String path, boolean html5Checks) {
+ assertChild1(node, path, "Child1", "type", "child1", html5Checks);
+ }
+ void assertChild1(JsonNode node, String path, String defName) {
+ assertChild1(node, path, defName, "type", "child1", false);
+ }
+ void assertChild1(JsonNode node, String path, String defName, String typeParamName, String typeName) {
+ assertChild1(node, path, defName, typeParamName, typeName, false);
+ }
+ void assertChild1(JsonNode node, String path, String defName, String typeParamName, String typeName, boolean html5Checks) {
+ var child1 = getNodeViaRefs(node, path, defName);
+ assertJsonSubTypesInfo(child1, typeParamName, typeName, html5Checks);
+ assertEquals (child1.at("/properties/parentString/type").asText(), "string");
+ assertEquals (child1.at("/properties/child1String/type").asText(), "string");
+ assertEquals (child1.at("/properties/_child1String2/type").asText(), "string");
+ assertEquals (child1.at("/properties/_child1String3/type").asText(), "string");
+ assertPropertyRequired(child1, "_child1String3", true);
+ }
+
+ void assertNullableChild1(JsonNode node, String path) {
+ assertNullableChild1(node, path, "Child1", false);
+ }
+ void assertNullableChild1(JsonNode node, String path, boolean html5Checks) {
+ assertNullableChild1(node, path, "Child1", html5Checks);
+ }
+ void assertNullableChild1(JsonNode node, String path, String defName) {
+ assertNullableChild1(node, path, defName, false);
+ }
+ void assertNullableChild1(JsonNode node, String path, String defName, boolean html5Checks) {
+ var child1 = getNodeViaRefs(node, path, defName);
+ assertJsonSubTypesInfo(child1, "type", "child1", html5Checks);
+ assertNullableType(child1, "/properties/parentString", "string");
+ assertNullableType(child1, "/properties/child1String", "string");
+ assertNullableType(child1, "/properties/_child1String2", "string");
+ assertEquals (child1.at("/properties/_child1String3/type").asText(), "string");
+ assertPropertyRequired(child1, "_child1String3", true);
+ }
+
+ void assertChild2(JsonNode node, String path) {
+ assertChild2(node, path, "Child2", "type", "child2", false);
+ }
+ void assertChild2(JsonNode node, String path, boolean html5Checks) {
+ assertChild2(node, path, "Child2", "type", "child2", html5Checks);
+ }
+ void assertChild2(JsonNode node, String path, String defName) {
+ assertChild2(node, path, defName, "type", "child2", false);
+ }
+ void assertChild2(JsonNode node, String path, String defName, String typeParamName, String typeName) {
+ assertChild2(node, path, defName, typeParamName, typeName, false);
+ }
+ void assertChild2(JsonNode node, String path, String defName, String typeParamName, String typeName, boolean html5Checks) {
+ var child2 = getNodeViaRefs(node, path, defName);
+ assertJsonSubTypesInfo(child2, typeParamName, typeName, html5Checks);
+ assertEquals (child2.at("/properties/parentString/type").asText(), "string");
+ assertEquals (child2.at("/properties/child2int/type").asText(), "integer");
+ }
+
+ void assertNullableChild2(JsonNode node, String path) {
+ assertNullableChild2(node, path, "Child2", false);
+ }
+ void assertNullableChild2(JsonNode node, String path, boolean html5Checks) {
+ assertNullableChild2(node, path, "Child2", html5Checks);
+ }
+ void assertNullableChild2(JsonNode node, String path, String defName) {
+ assertNullableChild2(node, path, defName, false);
+ }
+ void assertNullableChild2(JsonNode node, String path, String defName, boolean html5Checks) {
+ var child2 = getNodeViaRefs(node, path, defName);
+ assertJsonSubTypesInfo(child2, "type", "child2", html5Checks);
+ assertNullableType(child2, "/properties/parentString", "string");
+ assertNullableType(child2, "/properties/child2int", "integer");
+ }
+
+ @Test void generateSchemaForSuperClassAnnotatedWithJsonTypeInfo_use_IdNAME() {
+ var jsonNode = assertToFromJson(jsonSchemaGenerator, testData.child1);
+ assertToFromJson(jsonSchemaGenerator, testData.child1, Parent.class);
+
+ var schema = generateAndValidateSchema(jsonSchemaGenerator, Parent.class, jsonNode);
+
+ assertChild1(schema, "/oneOf");
+ assertChild2(schema, "/oneOf");
+ }
+
+ @Test void generateSchemaForSuperClassAnnotatedWithJsonTypeInfo_use_IdNAME_Nullables() {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorNullable, testData.child1);
+ assertToFromJson(jsonSchemaGeneratorNullable, testData.child1, Parent.class);
+
+ var schema = generateAndValidateSchema(jsonSchemaGenerator, Parent.class, jsonNode);
+
+ assertChild1(schema, "/oneOf");
+ assertChild2(schema, "/oneOf");
+ }
+
+ @Test void generateSchemaForSuperClassAnnotatedWithJsonTypeInfo_use_IdCLASS() {
+ var config = JsonSchemaConfig.VANILLA;
+ var g = new JsonSchemaGenerator(objectMapper, config);
+
+ var jsonNode = assertToFromJson(g, testData.child21);
+ assertToFromJson(g, testData.child21, Parent2.class);
+
+ var schema = generateAndValidateSchema(g, Parent2.class, jsonNode);
+
+ assertChild1(schema, "/oneOf", "Child21", "clazz", "com.kjetland.jackson.jsonSchema.testData.polymorphism2.Child21");
+ assertChild2(schema, "/oneOf", "Child22", "clazz", "com.kjetland.jackson.jsonSchema.testData.polymorphism2.Child22");
+ }
+
+ @Test void generateSchemaForSuperClassAnnotatedWithJsonTypeInfo_use_IdMINIMALCLASS() {
+ var config = JsonSchemaConfig.VANILLA;
+ var g = new JsonSchemaGenerator(objectMapper, config);
+
+ var jsonNode = assertToFromJson(g, testData.child51);
+ assertToFromJson(g, testData.child51, Parent5.class);
+
+ var schema = generateAndValidateSchema(g, Parent5.class, jsonNode);
+
+ assertChild1(schema, "/oneOf", "Child51", "clazz", ".Child51");
+ assertChild2(schema, "/oneOf", "Child52", "clazz", ".Child52");
+
+ var embeddedTypeName = objectMapper.valueToTree(new Parent5.Child51InnerClass()).get("clazz").asText();
+ assertChild1(schema, "/oneOf", "Child51InnerClass", "clazz", embeddedTypeName);
+ }
+
+ @Test void generateSchemaForInterfaceAnnotatedWithJsonTypeInfo_use_IdMINIMALCLASS() {
+ var config = JsonSchemaConfig.VANILLA;
+ var g = new JsonSchemaGenerator(objectMapper, config);
+
+ var jsonNode = assertToFromJson(g, testData.child61);
+ assertToFromJson(g, testData.child61, Parent6.class);
+
+ var schema = generateAndValidateSchema(g, Parent6.class, jsonNode);
+
+ assertChild1(schema, "/oneOf", "Child61", "clazz", ".Child61");
+ assertChild2(schema, "/oneOf", "Child62", "clazz", ".Child62");
+ }
+
+ @Test void generateSchemaForSuperClassAnnotatedWithJsonTypeInfo_include_AsEXISTINGPROPERTY() {
+
+ var jsonNode = assertToFromJson(jsonSchemaGenerator, testData.child31);
+ assertToFromJson(jsonSchemaGenerator, testData.child31, Parent3.class);
+
+ var schema = generateAndValidateSchema(jsonSchemaGenerator, Parent3.class, jsonNode);
+
+ assertChild1(schema, "/oneOf", "Child31", "type", "child31");
+ assertChild2(schema, "/oneOf", "Child32", "type", "child32");
+ }
+
+ @Test void generateSchemaForSuperClassAnnotatedWithJsonTypeInfo_include_AsCUSTOM() {
+
+ var jsonNode1 = assertToFromJson(jsonSchemaGenerator, testData.child41);
+ var jsonNode2 = assertToFromJson(jsonSchemaGenerator, testData.child42);
+
+ var schema1 = generateAndValidateSchema(jsonSchemaGenerator, Child41.class, jsonNode1);
+ var schema2 = generateAndValidateSchema(jsonSchemaGenerator, Child42.class, jsonNode2);
+
+ assertJsonSubTypesInfo(schema1, "type", "Child41");
+ assertJsonSubTypesInfo(schema2, "type", "Child42");
+ }
+
+ @Test void generateSchemaForClassContainingGenericsWithSameBaseTypeButDifferentTypeArguments() {
+ var config = JsonSchemaConfig.VANILLA;
+ var g = new JsonSchemaGenerator(objectMapper, config);
+
+ var instance = new GenericClassContainer();
+ var jsonNode = assertToFromJson(g, instance);
+ assertToFromJson(g, instance, GenericClassContainer.class);
+
+ var schema = generateAndValidateSchema(g, GenericClassContainer.class, jsonNode);
+
+ assertEquals (schema.at("/definitions/BoringClass/properties/data/type").asText(), "integer");
+ assertEquals (schema.at("/definitions/GenericClass(String)/properties/data/type").asText(), "string");
+ assertEquals (schema.at("/definitions/GenericWithJsonTypeName(String)/properties/data/type").asText(), "string");
+ assertEquals (schema.at("/definitions/GenericClass(BoringClass)/properties/data/$ref").asText(), "#/definitions/BoringClass");
+ assertEquals (schema.at("/definitions/GenericClassTwo(String,GenericClass(BoringClass))/properties/data1/type").asText(), "string");
+ assertEquals (schema.at("/definitions/GenericClassTwo(String,GenericClass(BoringClass))/properties/data2/$ref").asText(), "#/definitions/GenericClass(BoringClass)");
+ }
+
+ @Test void failOnUnknownProperties() {
+ var jsonNode = assertToFromJson(jsonSchemaGenerator, testData.manyPrimitives);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator, testData.manyPrimitives.getClass(), jsonNode);
+
+ assertFalse (schema.at("/additionalProperties").asBoolean());
+ }
+
+ @Test void failOnUnknownPropertiesOff() {
+ var generator = new JsonSchemaGenerator(objectMapper,
+ JsonSchemaConfig.VANILLA.toBuilder().failOnUnknownProperties(false).build());
+ var jsonNode = assertToFromJson(generator, testData.manyPrimitives);
+ var schema = generateAndValidateSchema(generator, testData.manyPrimitives.getClass(), jsonNode);
+
+ assertTrue (schema.at("/additionalProperties").asBoolean());
+ }
+
+ @Test void primitives() {
+ var jsonNode = assertToFromJson(jsonSchemaGenerator, testData.manyPrimitives);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator, testData.manyPrimitives.getClass(), jsonNode);
+
+ assertEquals (schema.at("/properties/_string/type").asText(), "string");
+
+ assertEquals (schema.at("/properties/_integer/type").asText(), "integer");
+ assertPropertyRequired(schema, "_integer", false); // Should allow null by default
+
+ assertEquals (schema.at("/properties/_int/type").asText(), "integer");
+ assertTrue(isPropertyRequired(schema, "_int"));
+
+ assertEquals (schema.at("/properties/_booleanObject/type").asText(), "boolean");
+ assertPropertyRequired(schema, "_booleanObject", false); // Should allow null by default
+
+ assertEquals (schema.at("/properties/_booleanPrimitive/type").asText(), "boolean");
+ assertPropertyRequired(schema, "_booleanPrimitive", true); // Must be required since it must have true or false - not null
+
+ assertEquals (schema.at("/properties/_booleanObjectWithNotNull/type").asText(), "boolean");
+ assertPropertyRequired(schema, "_booleanObjectWithNotNull", true);
+
+ assertEquals (schema.at("/properties/_doubleObject/type").asText(), "number");
+ assertPropertyRequired(schema, "_doubleObject", false); // Should allow null by default
+
+ assertEquals (schema.at("/properties/_doublePrimitive/type").asText(), "number");
+ assertPropertyRequired(schema, "_doublePrimitive", true); // Must be required since it must have a value - not null
+
+ assertEquals (schema.at("/properties/myEnum/type").asText(), "string");
+ assertEquals (getArrayNodeAsListOfStrings(schema.at("/properties/myEnum/enum")),
+ Stream.of(MyEnum.values()).map(Enum::name).toList());
+ assertEquals (schema.at("/properties/myEnum/JsonSchemaInjectOnEnum").asText(), "true");
+ }
+
+ @Test void nullables() {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorNullable, testData.manyPrimitivesNulls);
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorNullable, testData.manyPrimitivesNulls.getClass(), jsonNode);
+
+ assertNullableType(schema, "/properties/_string", "string");
+ assertNullableType(schema, "/properties/_integer", "integer");
+ assertNullableType(schema, "/properties/_booleanObject", "boolean");
+ assertNullableType(schema, "/properties/_doubleObject", "number");
+
+ // We're actually going to test this elsewhere, because if we set this to null here it'll break the "generateAndValidateSchema"
+ // test. What's fun is that the type system will allow you to set the value as null, but the schema won't (because there's a @NotNull annotation on it).
+ assertEquals (schema.at("/properties/_booleanObjectWithNotNull/type").asText(), "boolean");
+ assertPropertyRequired(schema, "_booleanObjectWithNotNull", true);
+
+ assertEquals (schema.at("/properties/_int/type").asText(), "integer");
+ assertPropertyRequired(schema, "_int", true);
+
+ assertEquals (schema.at("/properties/_booleanPrimitive/type").asText(), "boolean");
+ assertPropertyRequired(schema, "_booleanPrimitive", true);
+
+ assertEquals (schema.at("/properties/_doublePrimitive/type").asText(), "number");
+ assertPropertyRequired(schema, "_doublePrimitive", true);
+
+ assertNullableType(schema, "/properties/myEnum", "string");
+ assertEquals (getArrayNodeAsListOfStrings(schema.at("/properties/myEnum/oneOf/1/enum")),
+ Stream.of(MyEnum.values()).map(Enum::name).toList());
+ }
+
+ @Test void optional() {
+ var jsonNode = assertToFromJson(jsonSchemaGenerator, testData.pojoUsingOptionalJava);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator, testData.pojoUsingOptionalJava.getClass(), jsonNode);
+
+ assertEquals (schema.at("/properties/_string/type").asText(), "string");
+ assertPropertyRequired(schema, "_string", false); // Should allow null by default
+
+ assertEquals (schema.at("/properties/_integer/type").asText(), "integer");
+ assertPropertyRequired(schema, "_integer", false); // Should allow null by default
+
+ var child1 = getNodeViaRefs(schema, schema.at("/properties/child1"), "Child1");
+
+ assertJsonSubTypesInfo(child1, "type", "child1");
+ assertEquals (child1.at("/properties/parentString/type").asText(), "string");
+ assertEquals (child1.at("/properties/child1String/type").asText(), "string");
+ assertEquals (child1.at("/properties/_child1String2/type").asText(), "string");
+ assertEquals (child1.at("/properties/_child1String3/type").asText(), "string");
+
+ assertEquals (schema.at("/properties/optionalList/type").asText(), "array");
+ assertEquals (schema.at("/properties/optionalList/items/$ref").asText(), "#/definitions/ClassNotExtendingAnything");
+ }
+
+ @Test void nullableOptional() {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorNullable, testData.pojoUsingOptionalJava);
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorNullable, testData.pojoUsingOptionalJava.getClass(), jsonNode);
+
+ assertNullableType(schema, "/properties/_string", "string");
+ assertNullableType(schema, "/properties/_integer", "integer");
+
+ var child1 = getNodeViaRefs(schema, schema.at("/properties/child1/oneOf/1"), "Child1");
+
+ assertJsonSubTypesInfo(child1, "type", "child1");
+ assertNullableType(child1, "/properties/parentString", "string");
+ assertNullableType(child1, "/properties/child1String", "string");
+ assertNullableType(child1, "/properties/_child1String2", "string");
+ assertEquals (child1.at("/properties/_child1String3/type").asText(), "string");
+
+ assertNullableType(schema, "/properties/optionalList", "array");
+ assertEquals (schema.at("/properties/optionalList/oneOf/1/items/$ref").asText(), "#/definitions/ClassNotExtendingAnything");
+ }
+
+ @Test void customSerializerNotOverridingJsonSerializer_acceptJsonFormatVisitor() {
+ var jsonNode = assertToFromJson(jsonSchemaGenerator, testData.pojoWithCustomSerializer);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator, testData.pojoWithCustomSerializer.getClass(), jsonNode);
+ assertEquals (toList(schema.fieldNames()), List.of("$schema", "title")); // Empty schema due to custom serializer
+ }
+
+ @Test void objectWithPropertyUsingCustomSerializerNotOverridingJsonSerializer_acceptJsonFormatVisitor() {
+ var jsonNode = assertToFromJson(jsonSchemaGenerator, testData.objectWithPropertyWithCustomSerializer);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator, testData.objectWithPropertyWithCustomSerializer.getClass(), jsonNode);
+ assertEquals (schema.at("/properties/s/type").asText(), "string");
+ assertEquals (toList(schema.at("/properties/child").fieldNames()), List.of());
+ }
+
+ void pojoWithArrays_impl(Object pojo, Class> clazz, JsonSchemaGenerator g, boolean html5Checks) {
+ var jsonNode = assertToFromJson(g, pojo);
+ var schema = generateAndValidateSchema(g, clazz, jsonNode);
+
+ assertEquals (schema.at("/properties/intArray1/type").asText(), "array");
+ assertEquals (schema.at("/properties/intArray1/items/type").asText(), "integer");
+
+ assertEquals (schema.at("/properties/stringArray/type").asText(), "array");
+ assertEquals (schema.at("/properties/stringArray/items/type").asText(), "string");
+
+ assertEquals (schema.at("/properties/stringList/type").asText(), "array");
+ assertEquals (schema.at("/properties/stringList/items/type").asText(), "string");
+ assertTrue (schema.at("/properties/stringList/minItems").asInt() == 1);
+ assertTrue (schema.at("/properties/stringList/maxItems").asInt() == 10);
+
+ assertEquals (schema.at("/properties/polymorphismList/type").asText(), "array");
+ assertChild1(schema, "/properties/polymorphismList/items/oneOf", html5Checks);
+ assertChild2(schema, "/properties/polymorphismList/items/oneOf", html5Checks);
+
+ assertEquals (schema.at("/properties/polymorphismArray/type").asText(), "array");
+ assertChild1(schema, "/properties/polymorphismArray/items/oneOf", html5Checks);
+ assertChild2(schema, "/properties/polymorphismArray/items/oneOf", html5Checks);
+
+ assertEquals (schema.at("/properties/listOfListOfStrings/type").asText(), "array");
+ assertEquals (schema.at("/properties/listOfListOfStrings/items/type").asText(), "array");
+ assertEquals (schema.at("/properties/listOfListOfStrings/items/items/type").asText(), "string");
+
+ assertEquals (schema.at("/properties/setOfUniqueValues/type").asText(), "array");
+ assertEquals (schema.at("/properties/setOfUniqueValues/items/type").asText(), "string");
+
+ if (html5Checks) {
+ assertEquals (schema.at("/properties/setOfUniqueValues/uniqueItems").asText(), "true");
+ assertEquals (schema.at("/properties/setOfUniqueValues/format").asText(), "checkbox");
+ }
+ }
+
+ @Test void pojoWithArrays() {
+ pojoWithArrays_impl(testData.pojoWithArrays, testData.pojoWithArrays.getClass(), jsonSchemaGenerator, false);
+ pojoWithArrays_impl(testData.pojoWithArrays, testData.pojoWithArrays.getClass(), jsonSchemaGeneratorHTML5, true);
+ }
+
+ @Test void pojoWithArraysNullable() {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorNullable, testData.pojoWithArraysNullable);
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorNullable, testData.pojoWithArraysNullable.getClass(), jsonNode);
+
+ assertNullableType(schema, "/properties/intArray1", "array");
+ assertEquals (schema.at("/properties/intArray1/oneOf/1/items/type").asText(), "integer");
+
+ assertNullableType(schema, "/properties/stringArray", "array");
+ assertEquals (schema.at("/properties/stringArray/oneOf/1/items/type").asText(), "string");
+
+ assertNullableType(schema, "/properties/stringList", "array");
+ assertEquals (schema.at("/properties/stringList/oneOf/1/items/type").asText(), "string");
+
+ assertNullableType(schema, "/properties/polymorphismList", "array");
+ assertNullableChild1(schema, "/properties/polymorphismList/oneOf/1/items/oneOf");
+ assertNullableChild2(schema, "/properties/polymorphismList/oneOf/1/items/oneOf");
+
+ assertNullableType(schema, "/properties/polymorphismArray", "array");
+ assertNullableChild1(schema, "/properties/polymorphismArray/oneOf/1/items/oneOf");
+ assertNullableChild2(schema, "/properties/polymorphismArray/oneOf/1/items/oneOf");
+
+ assertNullableType(schema, "/properties/listOfListOfStrings", "array");
+ assertEquals (schema.at("/properties/listOfListOfStrings/oneOf/1/items/type").asText(), "array");
+ assertEquals (schema.at("/properties/listOfListOfStrings/oneOf/1/items/items/type").asText(), "string");
+ }
+
+ @Test void recursivePojo() {
+ var jsonNode = assertToFromJson(jsonSchemaGenerator, testData.recursivePojo);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator, testData.recursivePojo.getClass(), jsonNode);
+
+ assertEquals (schema.at("/properties/myText/type").asText(), "string");
+
+ assertEquals (schema.at("/properties/children/type").asText(), "array");
+ var defViaRef = getNodeViaRefs(schema, schema.at("/properties/children/items"), "RecursivePojo");
+
+ assertEquals (defViaRef.at("/properties/myText/type").asText(), "string");
+ assertEquals (defViaRef.at("/properties/children/type").asText(), "array");
+ var defViaRef2 = getNodeViaRefs(schema, defViaRef.at("/properties/children/items"), "RecursivePojo");
+
+ assertEquals (defViaRef, defViaRef2);
+ }
+
+ @Test void recursivePojoNullable() {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorNullable, testData.recursivePojo);
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorNullable, testData.recursivePojo.getClass(), jsonNode);
+
+ assertNullableType(schema, "/properties/myText", "string");
+
+ assertNullableType(schema, "/properties/children", "array");
+ var defViaRef = getNodeViaRefs(schema, schema.at("/properties/children/oneOf/1/items"), "RecursivePojo");
+
+ assertNullableType(defViaRef, "/properties/myText", "string");
+ assertNullableType(defViaRef, "/properties/children", "array");
+ var defViaRef2 = getNodeViaRefs(schema, defViaRef.at("/properties/children/oneOf/1/items"), "RecursivePojo");
+
+ assertEquals (defViaRef, defViaRef2);
+ }
+
+ @Test void pojoUsingMaps() {
+ var jsonNode = assertToFromJson(jsonSchemaGenerator, testData.pojoUsingMaps);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator, testData.pojoUsingMaps.getClass(), jsonNode);
+
+ assertEquals (schema.at("/properties/string2Integer/type").asText(), "object");
+ assertEquals (schema.at("/properties/string2Integer/additionalProperties/type").asText(), "integer");
+
+ assertEquals (schema.at("/properties/string2String/type").asText(), "object");
+ assertEquals (schema.at("/properties/string2String/additionalProperties/type").asText(), "string");
+
+ assertEquals (schema.at("/properties/string2PojoUsingJsonTypeInfo/type").asText(), "object");
+ assertEquals (schema.at("/properties/string2PojoUsingJsonTypeInfo/additionalProperties/oneOf/0/$ref").asText(), "#/definitions/Child1");
+ assertEquals (schema.at("/properties/string2PojoUsingJsonTypeInfo/additionalProperties/oneOf/1/$ref").asText(), "#/definitions/Child2");
+ }
+
+ @Test void pojoUsingMapsNullable() {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorNullable, testData.pojoUsingMaps);
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorNullable, testData.pojoUsingMaps.getClass(), jsonNode);
+
+ assertNullableType(schema, "/properties/string2Integer", "object");
+ assertEquals (schema.at("/properties/string2Integer/oneOf/1/additionalProperties/type").asText(), "integer");
+
+ assertNullableType(schema, "/properties/string2String", "object");
+ assertEquals (schema.at("/properties/string2String/oneOf/1/additionalProperties/type").asText(), "string");
+
+ assertNullableType(schema, "/properties/string2PojoUsingJsonTypeInfo", "object");
+ assertEquals (schema.at("/properties/string2PojoUsingJsonTypeInfo/oneOf/1/additionalProperties/oneOf/0/$ref").asText(), "#/definitions/Child1");
+ assertEquals (schema.at("/properties/string2PojoUsingJsonTypeInfo/oneOf/1/additionalProperties/oneOf/1/$ref").asText(), "#/definitions/Child2");
+ }
+
+ @Test void pojoUsingCustomAnnotations() {
+ var jsonNode = assertToFromJson(jsonSchemaGenerator, testData.pojoUsingFormat);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator, testData.pojoUsingFormat.getClass(), jsonNode);
+ var schemaHTML5Date = generateAndValidateSchema(jsonSchemaGeneratorHTML5, testData.pojoUsingFormat.getClass(), jsonNode);
+ var schemaHTML5DateNullable = generateAndValidateSchema(jsonSchemaGeneratorHTML5Nullable, testData.pojoUsingFormat.getClass(), jsonNode);
+
+ assertEquals (schema.at("/format").asText(), "grid");
+ assertEquals (schema.at("/description").asText(), "This is our pojo");
+ assertEquals (schema.at("/title").asText(), "Pojo using format");
+
+
+ assertEquals (schema.at("/properties/emailValue/type").asText(), "string");
+ assertEquals (schema.at("/properties/emailValue/format").asText(), "email");
+ assertEquals (schema.at("/properties/emailValue/description").asText(), "This is our email value");
+ assertEquals (schema.at("/properties/emailValue/title").asText(), "Email value");
+
+ assertEquals (schema.at("/properties/choice/type").asText(), "boolean");
+ assertEquals (schema.at("/properties/choice/format").asText(), "checkbox");
+
+ assertEquals (schema.at("/properties/dateTime/type").asText(), "string");
+ assertEquals (schema.at("/properties/dateTime/format").asText(), "date-time");
+ assertEquals (schema.at("/properties/dateTime/description").asText(), "This is description from @JsonPropertyDescription");
+ assertEquals (schemaHTML5Date.at("/properties/dateTime/format").asText(), "datetime");
+ assertEquals (schemaHTML5DateNullable.at("/properties/dateTime/oneOf/1/format").asText(), "datetime");
+
+
+ assertEquals (schema.at("/properties/dateTimeWithAnnotation/type").asText(), "string");
+ assertEquals (schema.at("/properties/dateTimeWithAnnotation/format").asText(), "text");
+
+ // Make sure autoGenerated title is correct;
+ assertEquals (schemaHTML5Date.at("/properties/dateTimeWithAnnotation/title").asText(), "Date Time With Annotation");
+ }
+
+ @Test void usingJavaType() {
+ var jsonNode = assertToFromJson(jsonSchemaGenerator, testData.pojoUsingFormat);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator, objectMapper.constructType(testData.pojoUsingFormat.getClass()), jsonNode);
+
+ assertEquals (schema.at("/format").asText(), "grid");
+ assertEquals (schema.at("/description").asText(), "This is our pojo");
+ assertEquals (schema.at("/title").asText(), "Pojo using format");
+
+
+ assertEquals (schema.at("/properties/emailValue/type").asText(), "string");
+ assertEquals (schema.at("/properties/emailValue/format").asText(), "email");
+ assertEquals (schema.at("/properties/emailValue/description").asText(), "This is our email value");
+ assertEquals (schema.at("/properties/emailValue/title").asText(), "Email value");
+
+ assertEquals (schema.at("/properties/choice/type").asText(), "boolean");
+ assertEquals (schema.at("/properties/choice/format").asText(), "checkbox");
+
+ assertEquals (schema.at("/properties/dateTime/type").asText(), "string");
+ assertEquals (schema.at("/properties/dateTime/format").asText(), "date-time");
+ assertEquals (schema.at("/properties/dateTime/description").asText(), "This is description from @JsonPropertyDescription");
+
+
+ assertEquals (schema.at("/properties/dateTimeWithAnnotation/type").asText(), "string");
+ assertEquals (schema.at("/properties/dateTimeWithAnnotation/format").asText(), "text");
+ }
+
+ @Test void usingJavaTypeWithJsonTypeName() {
+ var config = JsonSchemaConfig.VANILLA;
+ var g = new JsonSchemaGenerator(objectMapper, config);
+
+ var instance = new BoringContainer();
+ instance.child1 = new PojoUsingJsonTypeName();
+ instance.child1.stringWithDefault = "test";
+ var jsonNode = assertToFromJson(g, instance);
+ assertToFromJson(g, instance, BoringContainer.class);
+
+ var schema = generateAndValidateSchema(g, BoringContainer.class, jsonNode);
+
+ assertEquals (schema.at("/definitions/OtherTypeName/type").asText(), "object");
+ }
+
+ @Test void javaOptionalJsonEditor() {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorHTML5, testData.pojoUsingOptionalJava);
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorHTML5, testData.pojoUsingOptionalJava.getClass(), jsonNode);
+
+ assertNullableType(schema, "/properties/_string", "string");
+ assertEquals (schema.at("/properties/_string/title").asText(), "_string");
+
+ assertNullableType(schema, "/properties/_integer", "integer");
+ assertEquals (schema.at("/properties/_integer/title").asText(), "_integer");
+
+ assertEquals (schema.at("/properties/child1/oneOf/0/type").asText(), "null");
+ assertEquals (schema.at("/properties/child1/oneOf/0/title").asText(), "Not included");
+ var child1 = getNodeViaRefs(schema, schema.at("/properties/child1/oneOf/1"), "Child1");
+ assertEquals (schema.at("/properties/child1/title").asText(), "Child 1");
+
+ assertJsonSubTypesInfo(child1, "type", "child1", true);
+ assertEquals (child1.at("/properties/parentString/type").asText(), "string");
+ assertEquals (child1.at("/properties/child1String/type").asText(), "string");
+ assertEquals (child1.at("/properties/_child1String2/type").asText(), "string");
+ assertEquals (child1.at("/properties/_child1String3/type").asText(), "string");
+
+ assertNullableType(schema, "/properties/optionalList", "array");
+ assertEquals (schema.at("/properties/optionalList/oneOf/1/items/$ref").asText(), "#/definitions/ClassNotExtendingAnything");
+ assertEquals (schema.at("/properties/optionalList/title").asText(), "Optional List");
+ }
+
+ @Test void javaOptionalJsonEditorNullable() {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorHTML5Nullable, testData.pojoUsingOptionalJava);
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorHTML5Nullable, testData.pojoUsingOptionalJava.getClass(), jsonNode);
+
+ assertNullableType(schema, "/properties/_string", "string");
+ assertNullableType(schema, "/properties/_integer", "integer");
+
+ assertEquals (schema.at("/properties/child1/oneOf/0/type").asText(), "null");
+ assertEquals (schema.at("/properties/child1/oneOf/0/title").asText(), "Not included");
+ var child1 = getNodeViaRefs(schema, schema.at("/properties/child1/oneOf/1"), "Child1");
+
+ assertJsonSubTypesInfo(child1, "type", "child1", true);
+ assertNullableType(child1, "/properties/parentString", "string");
+ assertNullableType(child1, "/properties/child1String", "string");
+ assertNullableType(child1, "/properties/_child1String2", "string");
+
+ // This is required as we have a @JsonProperty marking it as so.;
+ assertEquals (child1.at("/properties/_child1String3/type").asText(), "string");
+ assertPropertyRequired(child1, "_child1String3", true);
+
+ assertNullableType(schema, "/properties/optionalList", "array");
+ assertEquals (schema.at("/properties/optionalList/oneOf/1/items/$ref").asText(), "#/definitions/ClassNotExtendingAnything");
+ assertEquals (schema.at("/properties/optionalList/title").asText(), "Optional List");
+ }
+
+ @Test void propertyOrdering() {
+ var jsonNode = assertToFromJson(jsonSchemaGenerator, testData.classNotExtendingAnything);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator, testData.classNotExtendingAnything.getClass(), jsonNode);
+
+ assertTrue (schema.at("/properties/someString/propertyOrder").isMissingNode());
+ }
+
+ @Test void propertyOrderingNullable() {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorNullable, testData.classNotExtendingAnything);
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorNullable, testData.classNotExtendingAnything.getClass(), jsonNode);
+
+ assertTrue (schema.at("/properties/someString/propertyOrder").isMissingNode());
+ }
+
+ @Test void propertyOrderingJsonEditor() {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorHTML5, testData.classNotExtendingAnything);
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorHTML5, testData.classNotExtendingAnything.getClass(), jsonNode);
+
+ assertEquals (schema.at("/properties/someString/propertyOrder").asInt(), 1);
+ assertEquals (schema.at("/properties/myEnum/propertyOrder").asInt(), 2);
+ }
+
+ @Test void propertyOrderingJsonEditorNullable() {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorHTML5Nullable, testData.classNotExtendingAnything);
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorHTML5Nullable, testData.classNotExtendingAnything.getClass(), jsonNode);
+
+ assertEquals (schema.at("/properties/someString/propertyOrder").asInt(), 1);
+ assertEquals (schema.at("/properties/myEnum/propertyOrder").asInt(), 2);
+ }
+
+ @Test void dates() {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorHTML5, testData.manyDates);
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorHTML5, testData.manyDates.getClass(), jsonNode);
+
+ assertEquals (schema.at("/properties/javaLocalDateTime/format").asText(), "datetime-local");
+ assertEquals (schema.at("/properties/javaOffsetDateTime/format").asText(), "datetime");
+ assertEquals (schema.at("/properties/javaLocalDate/format").asText(), "date");
+ assertEquals (schema.at("/properties/jodaLocalDate/format").asText(), "date");
+ }
+
+ @Test void defaultAndExamples() {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorHTML5, testData.defaultAndExamples);
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorHTML5, testData.defaultAndExamples.getClass(), jsonNode);
+
+ assertEquals (getArrayNodeAsListOfStrings(schema.at("/properties/emailValue/examples")), List.of("user@example.com"));
+ assertEquals (schema.at("/properties/fontSize/default").asText(), "12");
+ assertEquals (getArrayNodeAsListOfStrings(schema.at("/properties/fontSize/examples")), List.of("10", "14", "18"));
+
+ assertEquals (schema.at("/properties/defaultStringViaJsonValue/default").asText(), "ds");
+ assertEquals (schema.at("/properties/defaultIntViaJsonValue/default").asText(), "1");
+ assertEquals (schema.at("/properties/defaultBoolViaJsonValue/default").asText(), "true");
+ }
+
+ @Test void validation1() {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorHTML5, testData.classUsingValidation);
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorHTML5, testData.classUsingValidation.getClass(), jsonNode);
+
+ verifyStringProperty(schema, "stringUsingNotNull", 1, null, null, true);
+ verifyStringProperty(schema, "stringUsingNotBlank", 1, null, "^.*\\S+.*$", true);
+ verifyStringProperty(schema, "stringUsingNotBlankAndNotNull", 1, null, "^.*\\S+.*$", true);
+ verifyStringProperty(schema, "stringUsingNotEmpty", 1, null, null, true);
+ verifyStringProperty(schema, "stringUsingSize", 1, 20, null, false);
+ verifyStringProperty(schema, "stringUsingSizeOnlyMin", 1, null, null, false);
+ verifyStringProperty(schema, "stringUsingSizeOnlyMax", null, 30, null, false);
+ verifyStringProperty(schema, "stringUsingPattern", null, null, "_stringUsingPatternA|_stringUsingPatternB", false);
+ verifyStringProperty(schema, "stringUsingPatternList", null, null, "^(?=^_stringUsing.*)(?=.*PatternList$).*$", false);
+
+ verifyNumericProperty(schema, "intMin", 1, null, true);
+ verifyNumericProperty(schema, "intMax", null, 10, true);
+ verifyNumericProperty(schema, "doubleMin", 1, null, true);
+ verifyNumericProperty(schema, "doubleMax", null, 10, true);
+ verifyNumericDoubleProperty(schema, "decimalMin", 1.5, null, true);
+ verifyNumericDoubleProperty(schema, "decimalMax", null, 2.5, true);
+ assertEquals (schema.at("/properties/email/format").asText(), "email");
+
+ verifyArrayProperty(schema, "notEmptyStringArray", 1, null, true);
+
+ verifyObjectProperty(schema, "notEmptyMap", "string", 1, null, true);
+ }
+
+ @Test void validation2() {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorHTML5, testData.pojoUsingValidation);
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorHTML5, testData.pojoUsingValidation.getClass(), jsonNode);
+
+ verifyStringProperty(schema, "stringUsingNotNull", 1, null, null, true);
+ verifyStringProperty(schema, "stringUsingNotBlank", 1, null, "^.*\\S+.*$", true);
+ verifyStringProperty(schema, "stringUsingNotBlankAndNotNull", 1, null, "^.*\\S+.*$", true);
+ verifyStringProperty(schema, "stringUsingNotEmpty", 1, null, null, true);
+ verifyStringProperty(schema, "stringUsingSize", 1, 20, null, false);
+ verifyStringProperty(schema, "stringUsingSizeOnlyMin", 1, null, null, false);
+ verifyStringProperty(schema, "stringUsingSizeOnlyMax", null, 30, null, false);
+ verifyStringProperty(schema, "stringUsingPattern", null, null, "_stringUsingPatternA|_stringUsingPatternB", false);
+ verifyStringProperty(schema, "stringUsingPatternList", null, null, "^(?=^_stringUsing.*)(?=.*PatternList$).*$", false);
+
+ verifyNumericProperty(schema, "intMin", 1, null, true);
+ verifyNumericProperty(schema, "intMax", null, 10, true);
+ verifyNumericProperty(schema, "doubleMin", 1, null, true);
+ verifyNumericProperty(schema, "doubleMax", null, 10, true);
+ verifyNumericDoubleProperty(schema, "decimalMin", 1.5, null, true);
+ verifyNumericDoubleProperty(schema, "decimalMax", null, 2.5, true);
+
+ verifyArrayProperty(schema, "notEmptyStringArray", 1, null, true);
+ verifyArrayProperty(schema, "notEmptyStringList", 1, null, true);
+
+ verifyObjectProperty(schema, "notEmptyStringMap", "string", 1, null, true);
+ }
+
+ void verifyStringProperty(JsonNode schema, String propertyName, Integer minLength, Integer maxLength,
+ String pattern, boolean required) {
+ assertNumericPropertyValidation(schema, propertyName, "minLength", minLength);
+ assertNumericPropertyValidation(schema, propertyName, "maxLength", maxLength);
+
+ var matchNode = schema.at("/properties/"+propertyName+"/pattern");
+ if (pattern != null)
+ assertEquals (matchNode.asText(), pattern);
+ else
+ assertTrue (matchNode.isMissingNode());
+
+ assertPropertyRequired(schema, propertyName, required);
+ }
+
+ void verifyNumericProperty(JsonNode schema, String propertyName, Integer minimum, Integer maximum, boolean required) {
+ assertNumericPropertyValidation(schema, propertyName, "minimum", minimum);
+ assertNumericPropertyValidation(schema, propertyName, "maximum", maximum);
+ assertPropertyRequired(schema, propertyName, required);
+ }
+
+ void verifyNumericDoubleProperty(JsonNode schema, String propertyName, Double minimum, Double maximum, boolean required) {
+ assertNumericDoublePropertyValidation(schema, propertyName, "minimum", minimum);
+ assertNumericDoublePropertyValidation(schema, propertyName, "maximum", maximum);
+ assertPropertyRequired(schema, propertyName, required);
+ }
+
+ void verifyArrayProperty(JsonNode schema, String propertyName, Integer minItems, Integer maxItems, boolean required) {
+ assertNumericPropertyValidation(schema, propertyName, "minItems", minItems);
+ assertNumericPropertyValidation(schema, propertyName, "maxItems", maxItems);
+ assertPropertyRequired(schema, propertyName, required);
+ }
+
+ void verifyObjectProperty(JsonNode schema, String propertyName, String additionalPropertiesType, Integer minProperties, Integer maxProperties, boolean required) {
+ assertEquals (schema.at("/properties/"+propertyName+"/additionalProperties/type").asText(), additionalPropertiesType);
+ assertNumericPropertyValidation(schema, propertyName, "minProperties", minProperties);
+ assertNumericPropertyValidation(schema, propertyName, "maxProperties", maxProperties);
+ assertPropertyRequired(schema, propertyName, required);
+ }
+
+ void assertNumericPropertyValidation(JsonNode schema, String propertyName, String validationName, Integer value) {
+ var jsonNode = schema.at("/properties/"+propertyName+"/"+validationName+"");
+ if (value != null)
+ assertEquals (jsonNode.asInt(), value);
+ else
+ assertTrue (jsonNode.isMissingNode());
+ }
+
+ void assertNumericDoublePropertyValidation(JsonNode schema, String propertyName, String validationName, Double value) {
+ var jsonNode = schema.at("/properties/"+propertyName+"/"+validationName+"");
+ if (value != null)
+ assertEquals (jsonNode.asDouble(), value);
+ else
+ assertTrue (jsonNode.isMissingNode());
+ }
+
+ ClassUsingValidationWithGroups objectUsingGroups = testData.classUsingValidationWithGroups;
+
+ @Test void validationUsingNoGroups() {
+ var jsonSchemaGenerator_Group = new JsonSchemaGenerator(objectMapper,
+ JsonSchemaConfig.VANILLA.toBuilder().javaxValidationGroups(List.of()).build());
+
+ var jsonNode = assertToFromJson(jsonSchemaGenerator_Group, objectUsingGroups);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator_Group, objectUsingGroups.getClass(), jsonNode);
+
+ checkInjected(schema, "noGroup", true);
+ checkInjected(schema, "defaultGroup", true);
+ checkInjected(schema, "group1", false);
+ checkInjected(schema, "group2", false);
+ checkInjected(schema, "group12", false);
+
+ // Make sure inject on class-level is not included
+ assertTrue (schema.at("/injected").isMissingNode());
+ }
+
+ @Test void validationUsingDefaultGroup() {
+ var jsonSchemaGenerator_Group = new JsonSchemaGenerator(objectMapper,
+ JsonSchemaConfig.VANILLA.toBuilder().javaxValidationGroups(List.of(Default.class)).build());
+
+ var jsonNode = assertToFromJson(jsonSchemaGenerator_Group, objectUsingGroups);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator_Group, objectUsingGroups.getClass(), jsonNode);
+
+ checkInjected(schema, "noGroup", true);
+ checkInjected(schema, "defaultGroup", true);
+ checkInjected(schema, "group1", false);
+ checkInjected(schema, "group2", false);
+ checkInjected(schema, "group12", false);
+
+ // Make sure inject on class-level is not included
+ assertTrue (schema.at("/injected").isMissingNode());
+ }
+
+ @Test void validationUsingGroup1() {
+ var jsonSchemaGenerator_Group = new JsonSchemaGenerator(objectMapper,
+ JsonSchemaConfig.VANILLA.toBuilder().javaxValidationGroups(List.of(ValidationGroup1.class)).build());
+
+ var jsonNode = assertToFromJson(jsonSchemaGenerator_Group, objectUsingGroups);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator_Group, objectUsingGroups.getClass(), jsonNode);
+
+ checkInjected(schema, "noGroup", false);
+ checkInjected(schema, "defaultGroup", false);
+ checkInjected(schema, "group1", true);
+ checkInjected(schema, "group2", false);
+ checkInjected(schema, "group12", true);
+
+ // Make sure inject on class-level is included
+ assertFalse (schema.at("/injected").isMissingNode());
+ }
+
+ @Test void validationUsingGroup1AndDefault() {
+ var jsonSchemaGenerator_Group = new JsonSchemaGenerator(objectMapper,
+ JsonSchemaConfig.VANILLA.toBuilder().javaxValidationGroups(List.of(ValidationGroup1.class, Default.class)).build());
+
+ var jsonNode = assertToFromJson(jsonSchemaGenerator_Group, objectUsingGroups);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator_Group, objectUsingGroups.getClass(), jsonNode);
+
+ checkInjected(schema, "noGroup", true);
+ checkInjected(schema, "defaultGroup", true);
+ checkInjected(schema, "group1", true);
+ checkInjected(schema, "group2", false);
+ checkInjected(schema, "group12", true);
+
+ // Make sure inject on class-level is included
+ assertFalse (schema.at("/injected").isMissingNode());
+ }
+
+ @Test void validationUsingGroup2() {
+ var jsonSchemaGenerator_Group = new JsonSchemaGenerator(objectMapper,
+ JsonSchemaConfig.VANILLA.toBuilder().javaxValidationGroups(List.of(ValidationGroup2.class)).build());
+
+ var jsonNode = assertToFromJson(jsonSchemaGenerator_Group, objectUsingGroups);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator_Group, objectUsingGroups.getClass(), jsonNode);
+
+ checkInjected(schema, "noGroup", false);
+ checkInjected(schema, "defaultGroup", false);
+ checkInjected(schema, "group1", false);
+ checkInjected(schema, "group2", true);
+ checkInjected(schema, "group12", true);
+
+ // Make sure inject on class-level is not included;
+ assertTrue (schema.at("/injected").isMissingNode());
+ }
+
+ @Test void validationUsingGroup1and2() {
+ var jsonSchemaGenerator_Group = new JsonSchemaGenerator(objectMapper,
+ JsonSchemaConfig.VANILLA.toBuilder().javaxValidationGroups(List.of(ValidationGroup1.class, ValidationGroup2.class)).build());
+
+ var jsonNode = assertToFromJson(jsonSchemaGenerator_Group, objectUsingGroups);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator_Group, objectUsingGroups.getClass(), jsonNode);
+
+ checkInjected(schema, "noGroup", false);
+ checkInjected(schema, "defaultGroup", false);
+ checkInjected(schema, "group1", true);
+ checkInjected(schema, "group2", true);
+ checkInjected(schema, "group12", true);
+
+ // Make sure inject on class-level is included
+ assertFalse (schema.at("/injected").isMissingNode());
+ }
+
+ @Test void validationUsingGroup3() {
+ var jsonSchemaGenerator_Group = new JsonSchemaGenerator(objectMapper,
+ JsonSchemaConfig.VANILLA.toBuilder().javaxValidationGroups(List.of(ValidationGroup3_notInUse.class)).build());
+
+ var jsonNode = assertToFromJson(jsonSchemaGenerator_Group, objectUsingGroups);
+ var schema = generateAndValidateSchema(jsonSchemaGenerator_Group, objectUsingGroups.getClass(), jsonNode);
+
+ checkInjected(schema, "noGroup", false);
+ checkInjected(schema, "defaultGroup", false);
+ checkInjected(schema, "group1", false);
+ checkInjected(schema, "group2", false);
+ checkInjected(schema, "group12", false);
+
+ // Make sure inject on class-level is not included
+ assertTrue (schema.at("/injected").isMissingNode());
+ }
+
+ void checkInjected(JsonNode schema, String propertyName, boolean included) {
+ assertPropertyRequired(schema, propertyName, included);
+ assertNotEquals (schema.at("/properties/"+propertyName+"/injected").isMissingNode(), included);
+ }
+
+ @Test void polymorphismUsingMixin() {
+ var jsonNode = assertToFromJson(jsonSchemaGenerator, testData.mixinChild1);
+ assertToFromJson(jsonSchemaGenerator, testData.mixinChild1, MixinParent.class);
+
+ var schema = generateAndValidateSchema(jsonSchemaGenerator, MixinParent.class, jsonNode);
+
+ assertChild1(schema, "/oneOf", "MixinChild1");
+ assertChild2(schema, "/oneOf", "MixinChild2");
+ }
+
+ @Test void polymorphismUsingMixinNullable() {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorNullable, testData.mixinChild1);
+ assertToFromJson(jsonSchemaGeneratorNullable, testData.mixinChild1, MixinParent.class);
+
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorNullable, MixinParent.class, jsonNode);
+
+ assertNullableChild1(schema, "/oneOf", "MixinChild1");
+ assertNullableChild2(schema, "/oneOf", "MixinChild2");
+ }
+
+ @Test void issue24() throws JsonMappingException {
+ jsonSchemaGenerator.generateJsonSchema(EntityWrapper.class);
+ jsonSchemaGeneratorNullable.generateJsonSchema(EntityWrapper.class);
+ }
+
+ @Test void polymorphismOneOfOrdering() {
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorHTML5, PolymorphismOrdering.class, null);
+ List oneOfList = toList(schema.at("/oneOf").iterator()).stream().map(e -> e.at("/$ref").asText()).toList();
+ assertEquals (List.of("#/definitions/PolymorphismOrderingChild3", "#/definitions/PolymorphismOrderingChild1", "#/definitions/PolymorphismOrderingChild4", "#/definitions/PolymorphismOrderingChild2"), oneOfList);
+ }
+
+ @Test void notNullAnnotationsAndNullableTypes() {
+ var jsonNode = assertToFromJson(jsonSchemaGeneratorNullable, testData.notNullableButNullBoolean);
+ var schema = generateAndValidateSchema(jsonSchemaGeneratorNullable, testData.notNullableButNullBoolean.getClass(), null);
+
+ Exception exception = null;
+ try {
+ useSchema(schema, jsonNode);
+ }
+ catch (Exception e) {
+ exception = e;
+ }
+
+ // While our compiler will let us do what we're about to do, the validator should give us a message that looks like this...
+ assertTrue (exception.getMessage().contains("json does not validate against schema"));
+ assertTrue (exception.getMessage().contains("error: instance type (null) does not match any allowed primitive type (allowed: [\"boolean\"])"));
+
+ assertEquals (schema.at("/properties/notNullBooleanObject/type").asText(), "boolean");
+ assertPropertyRequired(schema, "notNullBooleanObject", true);
+ }
+
+ @Test void usingSchemaInject() throws JsonMappingException {
+ var customUserNameLoaderVariable = "xx";
+ var customUserNamesLoader = new CustomUserNamesLoader(customUserNameLoaderVariable);
+
+ var config = JsonSchemaConfig.VANILLA.toBuilder().jsonSuppliers(Map.of("myCustomUserNamesLoader", customUserNamesLoader)).build();
+ var _jsonSchemaGeneratorScala = new JsonSchemaGenerator(objectMapper, config);
+ var schema = _jsonSchemaGeneratorScala.generateJsonSchema(UsingJsonSchemaInject.class);
+
+ out.println("--------------------------------------------");
+ out.println(asPrettyJson(schema, _jsonSchemaGeneratorScala.objectMapper));
+
+ assertEquals (schema.at("/patternProperties/^s[a-zA-Z0-9]+/type").asText(), "string");
+ assertEquals (schema.at("/patternProperties/^i[a-zA-Z0-9]+/type").asText(), "integer");
+ assertEquals (schema.at("/properties/sa/type").asText(), "string");
+ assertEquals (schema.at("/properties/injectedInProperties").asText(), "true");
+ assertEquals (schema.at("/properties/sa/options/hidden").asText(), "true");
+ assertEquals (schema.at("/properties/saMergeFalse/type").asText(), "integer");
+ assertEquals (schema.at("/properties/saMergeFalse/default").asText(), "12");
+ assertTrue (schema.at("/properties/saMergeFalse/pattern").isMissingNode());
+ assertEquals (schema.at("/properties/ib/type").asText(), "integer");
+ assertEquals (schema.at("/properties/ib/multipleOf").asInt(), 7);
+ assertTrue (schema.at("/properties/ib/exclusiveMinimum").asBoolean());
+ assertEquals (schema.at("/properties/uns/items/enum/0").asText(), "foo");
+ assertEquals (schema.at("/properties/uns/items/enum/1").asText(), "bar");
+ assertEquals (schema.at("/properties/uns2/items/enum/0").asText(), "foo_" + customUserNameLoaderVariable);
+ assertEquals (schema.at("/properties/uns2/items/enum/1").asText(), "bar_" + customUserNameLoaderVariable);
+ }
+
+ @Test void usingJsonSchemaInjectWithTopLevelMergeFalse() throws JsonMappingException {
+ var config = JsonSchemaConfig.VANILLA;
+ var _jsonSchemaGeneratorScala = new JsonSchemaGenerator(objectMapper, config);
+ var schema = _jsonSchemaGeneratorScala.generateJsonSchema(UsingJsonSchemaInjectWithTopLevelMergeFalse.class);
+
+ var schemaJson = asPrettyJson(schema, _jsonSchemaGeneratorScala.objectMapper);
+ out.println("--------------------------------------------");
+ out.println(schemaJson);
+
+ var fasit = """
+ {
+ "everything" : "should be replaced"
+ }""".stripIndent();
+
+ assertTrue ( schemaJson .equals (fasit) );
+ }
+
+ @Test void preventingPolymorphismWithClassTypeRemapping_classWithProperty() {
+ var config = JsonSchemaConfig.VANILLA.toBuilder().classTypeReMapping(Map.of(Parent.class, Child1.class)).build();
+ var _jsonSchemaGenerator = new JsonSchemaGenerator(objectMapper, config);
+
+ // PojoWithParent has a property of type Parent (which uses polymorphism).
+ // Default rendering schema will make this property oneOf Child1 and Child2.
+ // In this test we're preventing this by remapping Parent to Child1.
+ // Now, when generating the schema, we should generate it as if the property where of type Child1
+
+ var jsonNode = assertToFromJson(_jsonSchemaGenerator, testData.pojoWithParent);
+ assertToFromJson(_jsonSchemaGenerator, testData.pojoWithParent, PojoWithParent.class);
+
+ var schema = generateAndValidateSchema(_jsonSchemaGenerator, PojoWithParent.class, jsonNode);
+
+ assertTrue (!schema.at("/additionalProperties").asBoolean());
+ assertEquals (schema.at("/properties/pojoValue/type").asText(), "boolean");
+ assertDefaultValues(schema);
+
+ assertChild1(schema, "/properties/child");
+ }
+
+ @Test void preventingPolymorphismWithClassTypeRemapping_rootClass() {
+ var config = JsonSchemaConfig.VANILLA.toBuilder().classTypeReMapping(Map.of(Parent.class, Child1.class)).build();
+ var _jsonSchemaGenerator = new JsonSchemaGenerator(objectMapper, config);
+
+ preventingPolymorphismWithClassTypeRemapping_rootClass_doTest(testData.child1, Parent.class, _jsonSchemaGenerator);
+ }
+ void preventingPolymorphismWithClassTypeRemapping_rootClass_doTest(Object pojo, Class> clazz, JsonSchemaGenerator g) {
+ var jsonNode = assertToFromJson(g, pojo);
+ var schema = generateAndValidateSchema(g, clazz, jsonNode);
+
+ assertTrue (!schema.at("/additionalProperties").asBoolean());
+ assertEquals (schema.at("/properties/parentString/type").asText(), "string");
+ assertJsonSubTypesInfo(schema, "type", "child1");
+ }
+
+ @Test void preventingPolymorphismWithClassTypeRemapping_arrays() {
+ var config = JsonSchemaConfig.VANILLA.toBuilder().classTypeReMapping(Map.of(Parent.class, Child1.class)).build();
+ var _jsonSchemaGenerator = new JsonSchemaGenerator(objectMapper, config);
+
+ var c = new Child1();
+ c.parentString = "pv";
+ c.child1String = "cs";
+ c.child1String2 = "cs2";
+ c.child1String3 = "cs3";
+
+ ClassNotExtendingAnything _classNotExtendingAnything = new ClassNotExtendingAnything();
+ _classNotExtendingAnything.someString = "Something";
+ _classNotExtendingAnything.myEnum = MyEnum.C;
+
+ var _pojoWithArrays = new PojoWithArrays(
+ new int[] {1,2,3},
+ new String[] {"a1","a2","a3"},
+ List.of("l1", "l2", "l3"),
+ List.of(c, c),
+ new Parent[] {c, c},
+ List.of(_classNotExtendingAnything, _classNotExtendingAnything),
+ Arrays.asList(Arrays.asList("1","2"), Arrays.asList("3")),
+ Set.of(MyEnum.B)
+ );
+
+ preventingPolymorphismWithClassTypeRemapping_arrays_doTest(_pojoWithArrays, _pojoWithArrays.getClass(), _jsonSchemaGenerator, false);
+ }
+
+ void preventingPolymorphismWithClassTypeRemapping_arrays_doTest(Object pojo, Class> clazz, JsonSchemaGenerator g, boolean html5Checks) {
+ var config = JsonSchemaConfig.VANILLA.toBuilder().classTypeReMapping(Map.of(Parent.class, Child1.class)).build();
+ var _jsonSchemaGenerator = new JsonSchemaGenerator(objectMapper, config);
+
+ var jsonNode = assertToFromJson(g, pojo);
+ var schema = generateAndValidateSchema(g, clazz, jsonNode);
+
+ assertEquals (schema.at("/properties/intArray1/type").asText(), "array");
+ assertEquals (schema.at("/properties/intArray1/items/type").asText(), "integer");
+
+ assertEquals (schema.at("/properties/stringArray/type").asText(), "array");
+ assertEquals (schema.at("/properties/stringArray/items/type").asText(), "string");
+
+ assertEquals (schema.at("/properties/stringList/type").asText(), "array");
+ assertEquals (schema.at("/properties/stringList/items/type").asText(), "string");
+ assertEquals (schema.at("/properties/stringList/minItems").asInt(), 1);
+ assertEquals (schema.at("/properties/stringList/maxItems").asInt(), 10);
+
+ assertEquals (schema.at("/properties/polymorphismList/type").asText(), "array");
+ assertChild1(schema, "/properties/polymorphismList/items", html5Checks);
+
+
+ assertEquals (schema.at("/properties/polymorphismArray/type").asText(), "array");
+ assertChild1(schema, "/properties/polymorphismArray/items", html5Checks);
+
+ assertEquals (schema.at("/properties/listOfListOfStrings/type").asText(), "array");
+ assertEquals (schema.at("/properties/listOfListOfStrings/items/type").asText(), "array");
+ assertEquals (schema.at("/properties/listOfListOfStrings/items/items/type").asText(), "string");
+
+ assertEquals (schema.at("/properties/setOfUniqueValues/type").asText(), "array");
+ assertEquals (schema.at("/properties/setOfUniqueValues/items/type").asText(), "string");
+
+ if (html5Checks) {
+ assertEquals (schema.at("/properties/setOfUniqueValues/uniqueItems").asText(), "true");
+ assertEquals (schema.at("/properties/setOfUniqueValues/format").asText(), "checkbox");
+ }
+ }
+
+ @Test void draft06() {
+ var jsg = jsonSchemaGenerator_draft_06;
+ var jsonNode = assertToFromJson(jsg, testData.classNotExtendingAnything);
+ var schema = generateAndValidateSchema(jsg, testData.classNotExtendingAnything.getClass(), jsonNode, JsonSchemaDraft.DRAFT_06);
+
+ // Currently there are no differences in the generated jsonSchema other than the $schema-url
+ }
+
+ @Test void draft07() {
+ var jsg = jsonSchemaGenerator_draft_07;
+ var jsonNode = assertToFromJson(jsg, testData.classNotExtendingAnything);
+ var schema = generateAndValidateSchema(jsg, testData.classNotExtendingAnything.getClass(), jsonNode, JsonSchemaDraft.DRAFT_07);
+
+ // Currently there are no differences in the generated jsonSchema other than the $schema-url
+ }
+
+ @Test void draft201909() {
+ var jsg = jsonSchemaGenerator_draft_2019_09;
+ var jsonNode = assertToFromJson(jsg, testData.classNotExtendingAnything);
+ var schema = generateAndValidateSchema(jsg, testData.classNotExtendingAnything.getClass(), jsonNode, JsonSchemaDraft.DRAFT_2019_09);
+
+ // Currently there are no differences in the generated jsonSchema other than the $schema-url
+ }
+}
diff --git a/src/test/java/com/kjetland/jackson/jsonSchema/TestUtils.java b/src/test/java/com/kjetland/jackson/jsonSchema/TestUtils.java
new file mode 100644
index 0000000..7662d86
--- /dev/null
+++ b/src/test/java/com/kjetland/jackson/jsonSchema/TestUtils.java
@@ -0,0 +1,227 @@
+package com.kjetland.jackson.jsonSchema;
+
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.MissingNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.github.fge.jsonschema.main.JsonSchemaFactory;
+import static java.lang.System.out;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.StreamSupport;
+import javax.annotation.Nullable;
+import lombok.SneakyThrows;
+import lombok.experimental.UtilityClass;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@UtilityClass // Do not static import!
+public final class TestUtils {
+
+ @SneakyThrows
+ String asPrettyJson(JsonNode node, ObjectMapper om) {
+ return om.writerWithDefaultPrettyPrinter().writeValueAsString(node);
+ }
+
+ // Asserts that we're able to go from object => json => equal object
+ @SneakyThrows
+ JsonNode assertToFromJson(JsonSchemaGenerator g, Object o) {
+ return assertToFromJson(g, o, o.getClass());
+ }
+
+ // Asserts that we're able to go from object => json => equal object
+ // desiredType might be a class which o extends (polymorphism)
+ @SneakyThrows
+ JsonNode assertToFromJson(JsonSchemaGenerator g, Object o, Class> desiredType) {
+ var json = g.objectMapper.writeValueAsString(o);
+ System.out.println("json: " + json);
+ var jsonNode = g.objectMapper.readTree(json);
+ var r = g.objectMapper.treeToValue(jsonNode, desiredType);
+ assertEquals (o , r);
+ return jsonNode;
+ }
+
+ @SneakyThrows
+ void useSchema(JsonNode schema, @Nullable JsonNode json) {
+ var schemaValidator = JsonSchemaFactory.byDefault().getJsonSchema(schema);
+ if (json != null) {
+ var r = schemaValidator.validate(json);
+ if (!r.isSuccess())
+ throw new Exception("json does not validate against schema: " + r);
+ }
+ }
+
+
+ /**
+ * Generates schema, validates the schema using external schema validator and
+ * Optionally tries to validate json against the schema.
+ */
+ @SneakyThrows
+ JsonNode generateAndValidateSchema(
+ JsonSchemaGenerator g,
+ Class> clazz,
+ @Nullable JsonNode jsonToTestAgainstSchema) {
+ return generateAndValidateSchema(g, clazz, jsonToTestAgainstSchema, JsonSchemaDraft.DRAFT_04);
+ }
+
+ /**
+ * Generates schema, validates the schema using external schema validator and
+ * Optionally tries to validate json against the schema.
+ */
+ @SneakyThrows
+ JsonNode generateAndValidateSchema(
+ JsonSchemaGenerator g,
+ Class> clazz,
+ @Nullable JsonNode jsonToTestAgainstSchema,
+ JsonSchemaDraft jsonSchemaDraft) {
+ var schema = g.generateJsonSchema(clazz);
+
+ out.println("--------------------------------------------");
+ out.println(asPrettyJson(schema, g.objectMapper));
+
+ assertEquals (jsonSchemaDraft.url, schema.at("/$schema").asText());
+
+ useSchema(schema, jsonToTestAgainstSchema);
+
+ return schema;
+ }
+
+ /**
+ * Generates schema, validates the schema using external schema validator and
+ * Optionally tries to validate json against the schema.
+ */
+ @SneakyThrows
+ JsonNode generateAndValidateSchema(
+ JsonSchemaGenerator g,
+ JavaType javaType,
+ @Nullable JsonNode jsonToTestAgainstSchema) {
+ return generateAndValidateSchema(g, javaType, jsonToTestAgainstSchema, JsonSchemaDraft.DRAFT_04);
+ }
+
+ /**
+ * Generates schema, validates the schema using external schema validator and
+ * Optionally tries to validate json against the schema.
+ */
+ @SneakyThrows
+ JsonNode generateAndValidateSchema(
+ JsonSchemaGenerator g,
+ JavaType javaType,
+ @Nullable JsonNode jsonToTestAgainstSchema,
+ JsonSchemaDraft jsonSchemaDraft) {
+
+ var schema = g.generateJsonSchema(javaType);
+
+ out.println("--------------------------------------------");
+ out.println(asPrettyJson(schema, g.objectMapper));
+
+ assertEquals (jsonSchemaDraft.url, schema.at("/$schema").asText());
+
+ useSchema(schema, jsonToTestAgainstSchema);
+
+ return schema;
+ }
+
+ void assertJsonSubTypesInfo(JsonNode node, String typeParamName, String typeName) {
+ assertJsonSubTypesInfo(node, typeParamName, typeName, false);
+ }
+
+ void assertJsonSubTypesInfo(JsonNode node, String typeParamName, String typeName, boolean html5Checks) {
+ /*
+ "properties" : {
+ "type" : {
+ "type" : "string",
+ "enum" : [ "child1" ],
+ "default" : "child1"
+ },
+ },
+ "title" : "child1",
+ "required" : [ "type" ]
+ */
+ assertEquals (node.at("/properties/" + typeParamName + "/type").asText(), "string");
+ assertEquals (node.at("/properties/" + typeParamName + "/enum/0").asText(), typeName);
+ assertEquals (node.at("/properties/" + typeParamName + "/default").asText(), typeName);
+ assertEquals (node.at("/title").asText(), typeName);
+ assertPropertyRequired(node, typeParamName, true);
+
+ if (html5Checks) {
+ assertTrue(node.at("/properties/" + typeParamName + "/options/hidden").asBoolean());
+ assertEquals (node.at("/options/multiple_editor_select_via_property/property").asText(), typeParamName);
+ assertEquals (node.at("/options/multiple_editor_select_via_property/value").asText(), typeName);
+ } else {
+ assertTrue(node.at("/options/multiple_editor_select_via_property/property") instanceof MissingNode);
+ }
+ }
+
+ List getArrayNodeAsListOfStrings(JsonNode node) {
+ if (node instanceof MissingNode)
+ return List.of();
+ else
+ return StreamSupport.stream(node.spliterator(), false).map(JsonNode::asText).toList();
+ }
+
+ List getRequiredList(JsonNode node) {
+ return getArrayNodeAsListOfStrings(node.at("/required"));
+ }
+
+ void assertPropertyRequired(JsonNode schema, String propertyName, boolean required) {
+ if (required) {
+ assertTrue (getRequiredList(schema).contains(propertyName));
+ } else {
+ assertFalse (getRequiredList(schema).contains(propertyName));
+ }
+ }
+
+ boolean isPropertyRequired(JsonNode schema, String propertyName) {
+ return getRequiredList(schema).contains(propertyName);
+ }
+
+ JsonNode getNodeViaRefs(JsonNode root, String pathToArrayOfRefs, String definitionName) {
+ List arrayItemNodes = new ArrayList<>();
+ var child = root.at(pathToArrayOfRefs);
+ if (child instanceof ArrayNode)
+ for (var e : child)
+ arrayItemNodes.add(e);
+ else
+ arrayItemNodes.add((ObjectNode)child);
+
+ var ref = arrayItemNodes.stream()
+ .map(a -> a.get("$ref").asText().substring(1))
+ .filter(a -> a.endsWith("/"+definitionName+""))
+ .findFirst().get();
+
+ return root.at(ref);
+ }
+
+ ObjectNode getNodeViaRefs(JsonNode root, JsonNode nodeWithRef, String definitionName) {
+ var ref = nodeWithRef.at("/$ref").asText();
+ assertTrue (ref.endsWith("/"+definitionName+""));
+ // use ref to look the node up
+ var fixedRef = ref.substring(1); // Removing starting #
+ return (ObjectNode) root.at(fixedRef);
+ }
+
+ List toList(Iterator iterator) {
+ var list = new ArrayList();
+ while (iterator.hasNext())
+ list.add (iterator.next());
+ return list;
+ }
+
+ void assertNullableType(JsonNode node, String path, String expectedType) {
+ var nullType = node.at(path).at("/oneOf/0");
+ assertEquals (nullType.at("/type").asText(), "null");
+ assertEquals (nullType.at("/title").asText(), "Not included");
+
+ var valueType = node.at(path).at("/oneOf/1");
+ assertEquals (valueType.at("/type").asText(), expectedType);
+
+ var pathParts = path.split("/");
+ var propName = pathParts[pathParts.length - 1];
+ assertFalse(getRequiredList(node).contains(propName));
+ }
+}
diff --git a/src/test/java/com/kjetland/jackson/jsonSchema/UseItFromJavaTest.java b/src/test/java/com/kjetland/jackson/jsonSchema/UseItFromJavaTest.java
index cbc250e..03548df 100755
--- a/src/test/java/com/kjetland/jackson/jsonSchema/UseItFromJavaTest.java
+++ b/src/test/java/com/kjetland/jackson/jsonSchema/UseItFromJavaTest.java
@@ -1,9 +1,11 @@
package com.kjetland.jackson.jsonSchema;
+import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.OffsetDateTime;
import java.util.*;
+import org.junit.jupiter.api.Test;
public class UseItFromJavaTest {
@@ -11,7 +13,8 @@ static class MyJavaPojo {
public String name;
}
- public UseItFromJavaTest() {
+ @Test
+ public void test() throws JsonMappingException {
// Just make sure it compiles
ObjectMapper objectMapper = new ObjectMapper();
JsonSchemaGenerator g1 = new JsonSchemaGenerator(objectMapper);
@@ -25,33 +28,30 @@ public UseItFromJavaTest() {
// Create custom JsonSchemaConfig from java
Map customMapping = new HashMap<>();
customMapping.put(OffsetDateTime.class.getName(), "date-time");
- JsonSchemaConfig config = JsonSchemaConfig.create(
- true,
- Optional.of("A"),
- true,
- true,
- true,
- true,
- true,
- true,
- true,
- customMapping,
- false,
- new HashSet<>(),
- new HashMap<>(),
- new HashMap<>(),
- null,
- true,
- null);
+ JsonSchemaConfig config = JsonSchemaConfig.builder()
+ .autoGenerateTitleForProperties(true)
+ .defaultArrayFormat("A")
+ .useOneOfForOption(false)
+ .useOneOfForNullables(true)
+ .usePropertyOrdering(true)
+ .hidePolymorphismTypeProperty(true)
+ .useMinLengthForNotNull(true)
+ .useTypeIdForDefinitionName(true)
+ .customType2FormatMapping(customMapping)
+ .useMultipleEditorSelectViaProperty(false)
+ .subclassesResolver(null)
+ .failOnUnknownProperties(true)
+ .javaxValidationGroups(null)
+ .build();
JsonSchemaGenerator g2 = new JsonSchemaGenerator(objectMapper, config);
-
// Config SubclassesResolving
-
- final SubclassesResolver subclassesResolver = new SubclassesResolverImpl()
- .withClassesToScan(Arrays.asList(
- "this.is.myPackage"
- ));
+ final SubclassesResolver subclassesResolver
+ = new SubclassesResolver
+ (null
+ , Arrays.asList(
+ "this.is.myPackage"
+ ));
}
}
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/BoringContainer.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/BoringContainer.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/BoringContainer.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/BoringContainer.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/ClassNotExtendingAnything.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/ClassNotExtendingAnything.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/ClassNotExtendingAnything.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/ClassNotExtendingAnything.java
diff --git a/src/test/java/com/kjetland/jackson/jsonSchema/testData/ClassUsingValidation.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/ClassUsingValidation.java
new file mode 100644
index 0000000..f263933
--- /dev/null
+++ b/src/test/java/com/kjetland/jackson/jsonSchema/testData/ClassUsingValidation.java
@@ -0,0 +1,66 @@
+package com.kjetland.jackson.jsonSchema.testData;
+
+import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaInject;
+import java.util.List;
+import java.util.Map;
+import javax.validation.constraints.*;
+import javax.validation.groups.Default;
+
+public record ClassUsingValidation
+(
+ @NotNull
+ String stringUsingNotNull,
+
+ @NotBlank
+ String stringUsingNotBlank,
+
+ @NotNull
+ @NotBlank
+ String stringUsingNotBlankAndNotNull,
+
+ @NotEmpty
+ String stringUsingNotEmpty,
+
+ @NotEmpty
+ List notEmptyStringArray, // Per PojoArraysWithScala, we use always use Lists in Scala, and never raw arrays.
+
+ @NotEmpty
+ Map notEmptyMap,
+
+ @Size(min=1, max=20)
+ String stringUsingSize,
+
+ @Size(min=1)
+ String stringUsingSizeOnlyMin,
+
+ @Size(max=30)
+ String stringUsingSizeOnlyMax,
+
+ @Pattern(regexp = "_stringUsingPatternA|_stringUsingPatternB")
+ String stringUsingPattern,
+
+ @Pattern.List({
+ @Pattern(regexp = "^_stringUsing.*"),
+ @Pattern(regexp = ".*PatternList$")
+ })
+ String stringUsingPatternList,
+
+ @Min(1)
+ int intMin,
+ @Max(10)
+ int intMax,
+ @Min(1)
+ double doubleMin,
+ @Max(10)
+ double doubleMax,
+ @DecimalMin("1.5")
+ double decimalMin,
+ @DecimalMax("2.5")
+ double decimalMax,
+
+ @Email
+ String email
+)
+{
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/kjetland/jackson/jsonSchema/testData/ClassUsingValidationWithGroups.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/ClassUsingValidationWithGroups.java
new file mode 100644
index 0000000..018de80
--- /dev/null
+++ b/src/test/java/com/kjetland/jackson/jsonSchema/testData/ClassUsingValidationWithGroups.java
@@ -0,0 +1,36 @@
+package com.kjetland.jackson.jsonSchema.testData;
+
+import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaInject;
+import com.kjetland.jackson.jsonSchema.testData.ClassUsingValidationWithGroups.ValidationGroup1;
+import javax.validation.constraints.NotNull;
+import javax.validation.groups.Default;
+
+
+@JsonSchemaInject(json = "{\"injected\":true}", javaxValidationGroups = { ValidationGroup1.class })
+public record ClassUsingValidationWithGroups
+(
+ @NotNull
+ @JsonSchemaInject(json = "{\"injected\":true}")
+ String noGroup,
+
+ @NotNull(groups = { Default.class })
+ @JsonSchemaInject(json = "{\"injected\":true}", javaxValidationGroups = { Default.class })
+ String defaultGroup,
+
+ @NotNull(groups = { ValidationGroup1.class })
+ @JsonSchemaInject(json = "{\"injected\":true}", javaxValidationGroups = { ValidationGroup1.class })
+ String group1,
+
+ @NotNull(groups = { ValidationGroup2.class })
+ @JsonSchemaInject(json = "{\"injected\":true}", javaxValidationGroups = { ValidationGroup2.class })
+ String group2,
+
+ @NotNull(groups = { ValidationGroup1.class, ValidationGroup2.class })
+ @JsonSchemaInject(json = "{\"injected\":true}", javaxValidationGroups = { ValidationGroup1.class, ValidationGroup2.class })
+ String group12
+)
+{
+ public interface ValidationGroup1 {}
+ public interface ValidationGroup2 {}
+ public interface ValidationGroup3_notInUse {}
+}
\ No newline at end of file
diff --git a/src/test/java/com/kjetland/jackson/jsonSchema/testData/DefaultAndExamples.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/DefaultAndExamples.java
new file mode 100644
index 0000000..9dd606b
--- /dev/null
+++ b/src/test/java/com/kjetland/jackson/jsonSchema/testData/DefaultAndExamples.java
@@ -0,0 +1,27 @@
+package com.kjetland.jackson.jsonSchema.testData;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaDefault;
+import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaExamples;
+
+
+public record DefaultAndExamples
+(
+ @JsonSchemaExamples({"user@example.com"})
+ String emailValue,
+
+ @JsonSchemaDefault("12")
+ @JsonSchemaExamples({"10", "14", "18"})
+ int fontSize,
+
+ @JsonProperty(defaultValue = "ds")
+ String defaultStringViaJsonValue,
+
+ @JsonProperty(defaultValue = "1")
+ int defaultIntViaJsonValue,
+
+ @JsonProperty(defaultValue = "true")
+ boolean defaultBoolViaJsonValue
+)
+{
+}
\ No newline at end of file
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/GenericClass.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/GenericClass.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/GenericClass.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/GenericClass.java
diff --git a/src/test/java/com/kjetland/jackson/jsonSchema/testData/ManyDates.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/ManyDates.java
new file mode 100644
index 0000000..281f1a0
--- /dev/null
+++ b/src/test/java/com/kjetland/jackson/jsonSchema/testData/ManyDates.java
@@ -0,0 +1,27 @@
+package com.kjetland.jackson.jsonSchema.testData;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import lombok.experimental.FieldDefaults;
+import lombok.extern.jackson.Jacksonized;
+
+/**
+ *
+ * @author alex
+ */
+@AllArgsConstructor
+@Jacksonized @Builder
+@FieldDefaults(level = AccessLevel.PUBLIC, makeFinal = true)
+@ToString @EqualsAndHashCode
+public class ManyDates {
+ LocalDateTime javaLocalDateTime;
+ OffsetDateTime javaOffsetDateTime;
+ LocalDate javaLocalDate;
+ org.joda.time.LocalDate jodaLocalDate;
+}
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/ManyPrimitives.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/ManyPrimitives.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/ManyPrimitives.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/ManyPrimitives.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/MapLike.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/MapLike.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/MapLike.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/MapLike.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/MyEnum.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/MyEnum.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/MyEnum.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/MyEnum.java
diff --git a/src/test/java/com/kjetland/jackson/jsonSchema/testData/NestedPolymorphism1Base.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/NestedPolymorphism1Base.java
new file mode 100644
index 0000000..41c99d7
--- /dev/null
+++ b/src/test/java/com/kjetland/jackson/jsonSchema/testData/NestedPolymorphism1Base.java
@@ -0,0 +1,11 @@
+package com.kjetland.jackson.jsonSchema.testData;
+
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
+@JsonSubTypes({
+ @JsonSubTypes.Type(value = NestedPolymorphism1_1.class, name = "NestedPolymorphism1_1"),
+ @JsonSubTypes.Type(value = NestedPolymorphism1_2.class, name = "NestedPolymorphism1_2")
+})
+public abstract class NestedPolymorphism1Base {}
diff --git a/src/test/java/com/kjetland/jackson/jsonSchema/testData/NestedPolymorphism1_1.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/NestedPolymorphism1_1.java
new file mode 100644
index 0000000..14fdc58
--- /dev/null
+++ b/src/test/java/com/kjetland/jackson/jsonSchema/testData/NestedPolymorphism1_1.java
@@ -0,0 +1,9 @@
+package com.kjetland.jackson.jsonSchema.testData;
+
+import lombok.AllArgsConstructor;
+
+@AllArgsConstructor
+public class NestedPolymorphism1_1 extends NestedPolymorphism1Base {
+ String a;
+ NestedPolymorphism2Base pojo;
+}
\ No newline at end of file
diff --git a/src/test/java/com/kjetland/jackson/jsonSchema/testData/NestedPolymorphism1_2.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/NestedPolymorphism1_2.java
new file mode 100644
index 0000000..c4be8c7
--- /dev/null
+++ b/src/test/java/com/kjetland/jackson/jsonSchema/testData/NestedPolymorphism1_2.java
@@ -0,0 +1,9 @@
+package com.kjetland.jackson.jsonSchema.testData;
+
+import lombok.AllArgsConstructor;
+
+@AllArgsConstructor
+public class NestedPolymorphism1_2 extends NestedPolymorphism1Base {
+ String a;
+ NestedPolymorphism2Base pojo;
+}
diff --git a/src/test/java/com/kjetland/jackson/jsonSchema/testData/NestedPolymorphism2Base.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/NestedPolymorphism2Base.java
new file mode 100644
index 0000000..f9aee20
--- /dev/null
+++ b/src/test/java/com/kjetland/jackson/jsonSchema/testData/NestedPolymorphism2Base.java
@@ -0,0 +1,11 @@
+package com.kjetland.jackson.jsonSchema.testData;
+
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
+@JsonSubTypes({
+ @JsonSubTypes.Type(value = NestedPolymorphism2_1.class, name = "NestedPolymorphism2_1"),
+ @JsonSubTypes.Type(value = NestedPolymorphism2_2.class, name = "NestedPolymorphism2_2")
+})
+public abstract class NestedPolymorphism2Base {}
diff --git a/src/test/java/com/kjetland/jackson/jsonSchema/testData/NestedPolymorphism2_1.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/NestedPolymorphism2_1.java
new file mode 100644
index 0000000..f16dac2
--- /dev/null
+++ b/src/test/java/com/kjetland/jackson/jsonSchema/testData/NestedPolymorphism2_1.java
@@ -0,0 +1,10 @@
+package com.kjetland.jackson.jsonSchema.testData;
+
+import java.util.Optional;
+import lombok.AllArgsConstructor;
+
+@AllArgsConstructor
+public class NestedPolymorphism2_1 extends NestedPolymorphism2Base {
+ String a;
+ Optional pojo;
+}
diff --git a/src/test/java/com/kjetland/jackson/jsonSchema/testData/NestedPolymorphism2_2.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/NestedPolymorphism2_2.java
new file mode 100644
index 0000000..9bc9133
--- /dev/null
+++ b/src/test/java/com/kjetland/jackson/jsonSchema/testData/NestedPolymorphism2_2.java
@@ -0,0 +1,10 @@
+package com.kjetland.jackson.jsonSchema.testData;
+
+import java.util.Optional;
+import lombok.AllArgsConstructor;
+
+@AllArgsConstructor
+public class NestedPolymorphism2_2 extends NestedPolymorphism2Base {
+ String a;
+ Optional pojo;
+}
diff --git a/src/test/java/com/kjetland/jackson/jsonSchema/testData/NestedPolymorphism3.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/NestedPolymorphism3.java
new file mode 100644
index 0000000..d3f4e25
--- /dev/null
+++ b/src/test/java/com/kjetland/jackson/jsonSchema/testData/NestedPolymorphism3.java
@@ -0,0 +1,9 @@
+package com.kjetland.jackson.jsonSchema.testData;
+
+import lombok.AllArgsConstructor;
+import lombok.RequiredArgsConstructor;
+
+@AllArgsConstructor
+public class NestedPolymorphism3 {
+ String b;
+}
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/ObjectWithPropertyWithCustomSerializer.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/ObjectWithPropertyWithCustomSerializer.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/ObjectWithPropertyWithCustomSerializer.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/ObjectWithPropertyWithCustomSerializer.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoUsingFormat.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoUsingFormat.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoUsingFormat.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoUsingFormat.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoUsingJsonTypeName.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoUsingJsonTypeName.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoUsingJsonTypeName.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoUsingJsonTypeName.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoUsingMaps.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoUsingMaps.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoUsingMaps.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoUsingMaps.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoUsingOptionalJava.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoUsingOptionalJava.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoUsingOptionalJava.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoUsingOptionalJava.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoUsingValidation.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoUsingValidation.java
similarity index 98%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoUsingValidation.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoUsingValidation.java
index 786c52a..3493b3c 100644
--- a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoUsingValidation.java
+++ b/src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoUsingValidation.java
@@ -1,7 +1,5 @@
package com.kjetland.jackson.jsonSchema.testData;
-import com.kjetland.jackson.jsonSchema.testDataScala.ClassUsingValidation;
-
import javax.validation.constraints.*;
import java.util.Arrays;
import java.util.List;
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoWithArrays.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoWithArrays.java
similarity index 95%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoWithArrays.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoWithArrays.java
index 0ee4562..577fb73 100755
--- a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoWithArrays.java
+++ b/src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoWithArrays.java
@@ -9,9 +9,6 @@
public class PojoWithArrays {
- // It was difficult to construct this from scala :)
- public static List> _listOfListOfStringsValues = Arrays.asList(Arrays.asList("1","2"), Arrays.asList("3"));
-
@NotNull
public int[] intArray1;
@NotNull
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoWithArraysNullable.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoWithArraysNullable.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoWithArraysNullable.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoWithArraysNullable.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoWithCustomSerializer.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoWithCustomSerializer.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoWithCustomSerializer.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoWithCustomSerializer.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoWithCustomSerializerDeserializer.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoWithCustomSerializerDeserializer.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoWithCustomSerializerDeserializer.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoWithCustomSerializerDeserializer.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoWithCustomSerializerSerializer.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoWithCustomSerializerSerializer.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoWithCustomSerializerSerializer.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoWithCustomSerializerSerializer.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoWithNotNull.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoWithNotNull.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoWithNotNull.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoWithNotNull.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoWithParent.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoWithParent.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/PojoWithParent.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/PojoWithParent.java
diff --git a/src/test/java/com/kjetland/jackson/jsonSchema/testData/PolymorphismOrdering.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/PolymorphismOrdering.java
new file mode 100644
index 0000000..b02cf0d
--- /dev/null
+++ b/src/test/java/com/kjetland/jackson/jsonSchema/testData/PolymorphismOrdering.java
@@ -0,0 +1,22 @@
+package com.kjetland.jackson.jsonSchema.testData;
+
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.kjetland.jackson.jsonSchema.testData.PolymorphismOrdering.PolymorphismOrderingChild1;
+import com.kjetland.jackson.jsonSchema.testData.PolymorphismOrdering.PolymorphismOrderingChild2;
+import com.kjetland.jackson.jsonSchema.testData.PolymorphismOrdering.PolymorphismOrderingChild3;
+import com.kjetland.jackson.jsonSchema.testData.PolymorphismOrdering.PolymorphismOrderingChild4;
+
+
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
+@JsonSubTypes({
+ @JsonSubTypes.Type(value = PolymorphismOrderingChild3.class, name = "PolymorphismOrderingChild3"),
+ @JsonSubTypes.Type(value = PolymorphismOrderingChild1.class, name = "PolymorphismOrderingChild1"),
+ @JsonSubTypes.Type(value = PolymorphismOrderingChild4.class, name = "PolymorphismOrderingChild4"),
+ @JsonSubTypes.Type(value = PolymorphismOrderingChild2.class, name = "PolymorphismOrderingChild2")})
+public interface PolymorphismOrdering {
+ public static class PolymorphismOrderingChild1 implements PolymorphismOrdering {}
+ public static class PolymorphismOrderingChild2 implements PolymorphismOrdering {}
+ public static class PolymorphismOrderingChild3 implements PolymorphismOrdering {}
+ public static class PolymorphismOrderingChild4 implements PolymorphismOrdering {}
+}
\ No newline at end of file
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/RecursivePojo.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/RecursivePojo.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/RecursivePojo.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/RecursivePojo.java
diff --git a/src/test/java/com/kjetland/jackson/jsonSchema/testData/TestData.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/TestData.java
new file mode 100644
index 0000000..529c8be
--- /dev/null
+++ b/src/test/java/com/kjetland/jackson/jsonSchema/testData/TestData.java
@@ -0,0 +1,212 @@
+package com.kjetland.jackson.jsonSchema.testData;
+
+import com.kjetland.jackson.jsonSchema.testData.mixin.MixinChild1;
+import com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child1;
+import com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child2;
+import com.kjetland.jackson.jsonSchema.testData.polymorphism1.Parent;
+import com.kjetland.jackson.jsonSchema.testData.polymorphism2.Child21;
+import com.kjetland.jackson.jsonSchema.testData.polymorphism2.Child22;
+import com.kjetland.jackson.jsonSchema.testData.polymorphism3.Child31;
+import com.kjetland.jackson.jsonSchema.testData.polymorphism3.Child32;
+import com.kjetland.jackson.jsonSchema.testData.polymorphism4.Child41;
+import com.kjetland.jackson.jsonSchema.testData.polymorphism4.Child42;
+import com.kjetland.jackson.jsonSchema.testData.polymorphism5.Child51;
+import com.kjetland.jackson.jsonSchema.testData.polymorphism5.Child52;
+import com.kjetland.jackson.jsonSchema.testData.polymorphism6.Child61;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import lombok.AccessLevel;
+import lombok.experimental.FieldDefaults;
+
+/**
+ *
+ * @author alex
+ */
+@FieldDefaults(level = AccessLevel.PUBLIC, makeFinal = true)
+public class TestData {
+ Child1 child1 = new Child1();
+ {
+ child1.parentString = "pv";
+ child1.child1String = "cs";
+ child1.child1String2 = "cs2";
+ child1.child1String3 = "cs3";
+ }
+ Child2 child2 = new Child2();
+ {
+ child2.parentString = "pv";
+ child2.child2int = 12;
+ }
+ PojoWithParent pojoWithParent = new PojoWithParent();
+ {
+ pojoWithParent.pojoValue = true;
+ pojoWithParent.child = child1;
+ pojoWithParent.stringWithDefault = "y";
+ pojoWithParent.intWithDefault = 13;
+ pojoWithParent.booleanWithDefault = true;
+ }
+
+ Child21 child21 = new Child21();
+ {
+ child21.parentString = "pv";
+ child21.child1String = "cs";
+ child21.child1String2 = "cs2";
+ child21.child1String3 = "cs3";
+ }
+ Child22 child22 = new Child22();
+ {
+ child22.parentString = "pv";
+ child22.child2int = 12;
+ }
+
+ Child31 child31 = new Child31();
+ {
+ child31.parentString = "pv";
+ child31.child1String = "cs";
+ child31.child1String2 = "cs2";
+ child31.child1String3 = "cs3";
+ }
+ Child32 child32 = new Child32();
+ {
+ child32.parentString = "pv";
+ child32.child2int = 12;
+ }
+
+ Child41 child41 = new Child41();
+ Child42 child42 = new Child42();
+
+ Child51 child51 = new Child51();
+ {
+ child51.parentString = "pv";
+ child51.child1String = "cs";
+ child51.child1String2 = "cs2";
+ child51.child1String3 = "cs3";
+ }
+ Child52 child52 = new Child52();
+ {
+ child52.parentString = "pv";
+ child52.child2int = 12;
+ }
+ Child61 child61 = new Child61();
+ {
+ child61.parentString = "pv";
+ child61.child1String = "cs";
+ child61.child1String2 = "cs2";
+ child61.child1String3 = "cs3";
+ }
+
+ ClassNotExtendingAnything classNotExtendingAnything = new ClassNotExtendingAnything();
+ {
+ classNotExtendingAnything.someString = "Something";
+ classNotExtendingAnything.myEnum = MyEnum.C;
+ }
+
+ ManyPrimitives manyPrimitives = new ManyPrimitives("s1", 1, 2, true, false, true, 0.1, 0.2, MyEnum.B);
+
+ ManyPrimitives manyPrimitivesNulls = new ManyPrimitives(null, null, 1, null, false, false, null, 0.1, null);
+
+ PojoUsingOptionalJava pojoUsingOptionalJava = new PojoUsingOptionalJava(Optional.of("s"), Optional.of(1), Optional.of(child1), Optional.of(Arrays.asList(classNotExtendingAnything)));
+
+ PojoWithCustomSerializer pojoWithCustomSerializer = new PojoWithCustomSerializer();
+ {
+ pojoWithCustomSerializer.myString = "xxx";
+ }
+
+ ObjectWithPropertyWithCustomSerializer objectWithPropertyWithCustomSerializer = new ObjectWithPropertyWithCustomSerializer("s1", pojoWithCustomSerializer);
+
+ PojoWithArrays pojoWithArrays = new PojoWithArrays(
+ new int[] { 1,2,3 },
+ new String[] { "a1", "a2", "a3" },
+ List.of("l1", "l2", "l3"),
+ List.of(child1, child2),
+ new Parent[] { child1, child2 },
+ List.of(classNotExtendingAnything, classNotExtendingAnything),
+ Arrays.asList(Arrays.asList("1","2"), Arrays.asList("3")),
+ Set.of(MyEnum.B)
+ );
+
+ PojoWithArraysNullable pojoWithArraysNullable = new PojoWithArraysNullable(
+ new int[] { 1, 2, 3 },
+ new String[] { "a1","a2","a3" },
+ List.of("l1", "l2", "l3"),
+ List.of(child1, child2),
+ new Parent[] { child1, child2 },
+ List.of(classNotExtendingAnything, classNotExtendingAnything),
+ Arrays.asList(Arrays.asList("1","2"), Arrays.asList("3")),
+ Set.of(MyEnum.B)
+ );
+
+ RecursivePojo recursivePojo = new RecursivePojo("t1", List.of(new RecursivePojo("c1", null)));
+
+ PojoUsingMaps pojoUsingMaps = new PojoUsingMaps(
+ Map.of("a", 1, "b", 2),
+ Map.of("x", "y", "z", "w"),
+ Map.of("1", child1, "2", child2)
+ );
+
+ PojoUsingFormat pojoUsingFormat = new PojoUsingFormat("test@example.com", true, OffsetDateTime.now(), OffsetDateTime.now());
+ ManyDates manyDates = new ManyDates(LocalDateTime.now(), OffsetDateTime.now(), LocalDate.now(), org.joda.time.LocalDate.now());
+
+ DefaultAndExamples defaultAndExamples = new DefaultAndExamples("email@example.com", 18, "s", 2, false);
+
+ ClassUsingValidation classUsingValidation = new ClassUsingValidation(
+ "_stringUsingNotNull",
+ "_stringUsingNotBlank",
+ "_stringUsingNotBlankAndNotNull",
+ "_stringUsingNotEmpty",
+ List.of("l1", "l2", "l3"),
+ Map.of("mk1", "mv1", "mk2", "mv2"),
+ "_stringUsingSize",
+ "_stringUsingSizeOnlyMin",
+ "_stringUsingSizeOnlyMax",
+ "_stringUsingPatternA",
+ "_stringUsingPatternList",
+ 1, 2, 1.0, 2.0, 1.6, 2.0,
+ "mbk@kjetland.com"
+ );
+
+ ClassUsingValidationWithGroups classUsingValidationWithGroups = new ClassUsingValidationWithGroups(
+ "_noGroup", "_defaultGroup", "_group1", "_group2", "_group12"
+ );
+
+ PojoUsingValidation pojoUsingValidation = new PojoUsingValidation(
+ "_stringUsingNotNull",
+ "_stringUsingNotBlank",
+ "_stringUsingNotBlankAndNotNull",
+ "_stringUsingNotEmpty",
+ new String[] { "a1", "a2", "a3" },
+ List.of("l1", "l2", "l3"),
+ Map.of("mk1", "mv1", "mk2", "mv2"),
+ "_stringUsingSize",
+ "_stringUsingSizeOnlyMin",
+ "_stringUsingSizeOnlyMax",
+ "_stringUsingPatternA",
+ "_stringUsingPatternList",
+ 1, 2, 1.0, 2.0, 1.6, 2.0
+ );
+
+ MixinChild1 mixinChild1 = new MixinChild1();
+ {
+ mixinChild1.parentString = "pv";
+ mixinChild1.child1String = "cs";
+ mixinChild1.child1String2 = "cs2";
+ mixinChild1.child1String3 = "cs3";
+ }
+
+ // Test the collision of @NotNull validations and null fields.
+ PojoWithNotNull notNullableButNullBoolean = new PojoWithNotNull(null);
+
+ NestedPolymorphism1_1 nestedPolymorphism = new NestedPolymorphism1_1("a1", new NestedPolymorphism2_2("a2", Optional.of(new NestedPolymorphism3("b3"))));
+
+ GenericClass.GenericClassVoid genericClassVoid = new GenericClass.GenericClassVoid();
+
+ MapLike.GenericMapLike genericMapLike = new MapLike.GenericMapLike(Collections.singletonMap("foo", "bar"));
+
+// KotlinWithDefaultValues kotlinWithDefaultValues = new KotlinWithDefaultValues("1", "2", "3", "4");
+}
diff --git a/src/test/java/com/kjetland/jackson/jsonSchema/testData/UsingJsonSchemaInjectTop.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/UsingJsonSchemaInjectTop.java
new file mode 100644
index 0000000..81c12f3
--- /dev/null
+++ b/src/test/java/com/kjetland/jackson/jsonSchema/testData/UsingJsonSchemaInjectTop.java
@@ -0,0 +1,117 @@
+package com.kjetland.jackson.jsonSchema.testData;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaBool;
+import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaInject;
+import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaInt;
+import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaString;
+import java.util.Set;
+import java.util.function.Supplier;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.Pattern;
+import lombok.RequiredArgsConstructor;
+
+/**
+ *
+ * @author alex
+ */
+public class UsingJsonSchemaInjectTop {
+ @JsonSchemaInject(
+ json=
+ """
+ {
+ "patternProperties": {
+ "^s[a-zA-Z0-9]+": {
+ "type": "string"
+ }
+ },
+ "properties": {
+ "injectedInProperties": "true"
+ }
+ }
+ """,
+ strings = {@JsonSchemaString(path = "patternProperties/^i[a-zA-Z0-9]+/type", value = "integer")}
+ )
+ public record UsingJsonSchemaInject
+ (
+ @JsonSchemaInject(
+ json=
+ """
+ {
+ "options": {
+ "hidden": true
+ }
+ }
+ """)
+ String sa,
+
+ @JsonSchemaInject(
+ json=
+ """
+ {
+ "type": "integer",
+ "default": 12
+ }
+ """,
+ overridesAll = true
+ )
+ @Pattern(regexp = "xxx") // Should not end up in schema since we're replacing with injected
+ String saMergeFalse,
+
+ @JsonSchemaInject(
+ bools = {@JsonSchemaBool(path = "exclusiveMinimum", value = true)},
+ ints = {@JsonSchemaInt(path = "multipleOf", value = 7)}
+ )
+ @Min(5)
+ int ib,
+
+ @JsonSchemaInject(jsonSupplier = UserNamesLoader.class)
+ Set uns,
+
+ @JsonSchemaInject(jsonSupplierViaLookup = "myCustomUserNamesLoader")
+ Set uns2
+ )
+ {}
+
+ public static class UserNamesLoader implements Supplier {
+ ObjectMapper _objectMapper = new ObjectMapper();
+
+ @Override public JsonNode get() {
+ var schema = _objectMapper.createObjectNode();
+ var values = schema.putObject("items").putArray("enum");
+ values.add("foo");
+ values.add("bar");
+
+ return schema;
+ }
+ }
+
+ @RequiredArgsConstructor
+ public static class CustomUserNamesLoader implements Supplier {
+ final String custom;
+ ObjectMapper _objectMapper = new ObjectMapper();
+
+ @Override public JsonNode get() {
+ var schema = _objectMapper.createObjectNode();
+ var values = schema.putObject("items").putArray("enum");
+ values.add("foo_"+custom);
+ values.add("bar_"+custom);
+
+ return schema;
+ }
+ }
+
+ @JsonSchemaInject(
+ json = """
+ {
+ "everything": "should be replaced"
+ }""",
+ overridesAll = true
+ )
+ public record UsingJsonSchemaInjectWithTopLevelMergeFalse
+ (
+ String shouldBeIgnored
+ )
+ {}
+}
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/generic/BoringClass.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/generic/BoringClass.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/generic/BoringClass.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/generic/BoringClass.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/generic/GenericClass.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/generic/GenericClass.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/generic/GenericClass.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/generic/GenericClass.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/generic/GenericClassContainer.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/generic/GenericClassContainer.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/generic/GenericClassContainer.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/generic/GenericClassContainer.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/generic/GenericClassTwo.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/generic/GenericClassTwo.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/generic/GenericClassTwo.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/generic/GenericClassTwo.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/generic/GenericClassWithJsonTypeName.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/generic/GenericClassWithJsonTypeName.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/generic/GenericClassWithJsonTypeName.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/generic/GenericClassWithJsonTypeName.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/mixin/MixinChild1.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/mixin/MixinChild1.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/mixin/MixinChild1.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/mixin/MixinChild1.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/mixin/MixinChild2.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/mixin/MixinChild2.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/mixin/MixinChild2.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/mixin/MixinChild2.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/mixin/MixinModule.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/mixin/MixinModule.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/mixin/MixinModule.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/mixin/MixinModule.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/mixin/MixinParent.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/mixin/MixinParent.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/mixin/MixinParent.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/mixin/MixinParent.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism1/Child1.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism1/Child1.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism1/Child1.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism1/Child1.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism1/Child2.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism1/Child2.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism1/Child2.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism1/Child2.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism1/Parent.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism1/Parent.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism1/Parent.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism1/Parent.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism2/Child21.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism2/Child21.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism2/Child21.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism2/Child21.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism2/Child22.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism2/Child22.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism2/Child22.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism2/Child22.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism2/Parent2.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism2/Parent2.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism2/Parent2.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism2/Parent2.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism3/Child31.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism3/Child31.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism3/Child31.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism3/Child31.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism3/Child32.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism3/Child32.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism3/Child32.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism3/Child32.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism3/Parent3.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism3/Parent3.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism3/Parent3.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism3/Parent3.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism4/Child41.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism4/Child41.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism4/Child41.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism4/Child41.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism4/Child42.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism4/Child42.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism4/Child42.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism4/Child42.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism4/Parent4.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism4/Parent4.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism4/Parent4.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism4/Parent4.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism4/TypeIdResolverBySimpleNameInPackage.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism4/TypeIdResolverBySimpleNameInPackage.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism4/TypeIdResolverBySimpleNameInPackage.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism4/TypeIdResolverBySimpleNameInPackage.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism5/Child51.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism5/Child51.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism5/Child51.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism5/Child51.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism5/Child52.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism5/Child52.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism5/Child52.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism5/Child52.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism5/Parent5.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism5/Parent5.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism5/Parent5.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism5/Parent5.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism6/Child61.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism6/Child61.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism6/Child61.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism6/Child61.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism6/Child62.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism6/Child62.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism6/Child62.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism6/Child62.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism6/Parent6.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism6/Parent6.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData/polymorphism6/Parent6.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData/polymorphism6/Parent6.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData_issue_24/AbstractObject.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData_issue_24/AbstractObject.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData_issue_24/AbstractObject.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData_issue_24/AbstractObject.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData_issue_24/Address.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData_issue_24/Address.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData_issue_24/Address.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData_issue_24/Address.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData_issue_24/Business.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData_issue_24/Business.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData_issue_24/Business.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData_issue_24/Business.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData_issue_24/EntityWrapper.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData_issue_24/EntityWrapper.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData_issue_24/EntityWrapper.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData_issue_24/EntityWrapper.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData_issue_24/Name.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData_issue_24/Name.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData_issue_24/Name.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData_issue_24/Name.java
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testData_issue_24/Person.java b/src/test/java/com/kjetland/jackson/jsonSchema/testData_issue_24/Person.java
similarity index 100%
rename from src/test/scala/com/kjetland/jackson/jsonSchema/testData_issue_24/Person.java
rename to src/test/java/com/kjetland/jackson/jsonSchema/testData_issue_24/Person.java
diff --git a/src/test/kotlin/com/kjetland/jackson/jsonSchema/KotlinClass.kt b/src/test/kotlin/com/kjetland/jackson/jsonSchema/KotlinClass.kt
deleted file mode 100644
index 7def702..0000000
--- a/src/test/kotlin/com/kjetland/jackson/jsonSchema/KotlinClass.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.kjetland.jackson.jsonSchema
-
-data class KotlinClass(
- val a:String,
- val b:Int
-)
-
-data class KotlinWithDefaultValues(
- val optional: String?,
- val required: String,
- val optionalDefault: String = "Hello",
- val optionalDefaultNull: String? = "Hello"
-)
diff --git a/src/test/resources/logback-TEST.xml b/src/test/resources/logback-TEST.xml
deleted file mode 100755
index c464d97..0000000
--- a/src/test/resources/logback-TEST.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
- %date{ISO8601} lvl=%level, %m | sId=%X{serviceId}, rId=%X{requestId}, akkaSrc=%X{akkaSource}, akkaThrd=%X{sourceThread}, thrd=%thread, lgr=%logger{36} %X{akkaPersistenceRecovering}%n
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/CreateJsonSchemaGeneratorFromJava.java b/src/test/scala/com/kjetland/jackson/jsonSchema/CreateJsonSchemaGeneratorFromJava.java
deleted file mode 100644
index b01c51f..0000000
--- a/src/test/scala/com/kjetland/jackson/jsonSchema/CreateJsonSchemaGeneratorFromJava.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.kjetland.jackson.jsonSchema;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import io.github.classgraph.ClassGraph;
-
-public class CreateJsonSchemaGeneratorFromJava {
- public CreateJsonSchemaGeneratorFromJava(ObjectMapper objectMapper) {
- JsonSchemaGenerator vanilla = new JsonSchemaGenerator(objectMapper);
- JsonSchemaGenerator html5 = new JsonSchemaGenerator(objectMapper, JsonSchemaConfig.html5EnabledSchema());
- JsonSchemaGenerator nullable = new JsonSchemaGenerator(objectMapper, JsonSchemaConfig.nullableJsonSchemaDraft4());
- }
-}
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGeneratorTest.scala b/src/test/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGeneratorTest.scala
deleted file mode 100755
index 751f941..0000000
--- a/src/test/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGeneratorTest.scala
+++ /dev/null
@@ -1,1911 +0,0 @@
-package com.kjetland.jackson.jsonSchema
-
-import java.time.{LocalDate, LocalDateTime, OffsetDateTime}
-import java.util
-import java.util.{Collections, Optional, TimeZone}
-
-import com.fasterxml.jackson.databind.module.SimpleModule
-import com.fasterxml.jackson.databind.node.{ArrayNode, MissingNode, ObjectNode}
-import com.fasterxml.jackson.databind.{JavaType, JsonNode, ObjectMapper, SerializationFeature}
-import com.fasterxml.jackson.datatype.jdk8.Jdk8Module
-import com.fasterxml.jackson.datatype.joda.JodaModule
-import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
-import com.fasterxml.jackson.module.kotlin.KotlinModule
-import com.fasterxml.jackson.module.scala.DefaultScalaModule
-import com.github.fge.jsonschema.main.JsonSchemaFactory
-import com.kjetland.jackson.jsonSchema.testData.GenericClass.GenericClassVoid
-import com.kjetland.jackson.jsonSchema.testData.MapLike.GenericMapLike
-import com.kjetland.jackson.jsonSchema.testData._
-import com.kjetland.jackson.jsonSchema.testData.generic.GenericClassContainer
-import com.kjetland.jackson.jsonSchema.testData.mixin.{MixinChild1, MixinModule, MixinParent}
-import com.kjetland.jackson.jsonSchema.testData.polymorphism1.{Child1, Child2, Parent}
-import com.kjetland.jackson.jsonSchema.testData.polymorphism2.{Child21, Child22, Parent2}
-import com.kjetland.jackson.jsonSchema.testData.polymorphism3.{Child31, Child32, Parent3}
-import com.kjetland.jackson.jsonSchema.testData.polymorphism4.{Child41, Child42}
-import com.kjetland.jackson.jsonSchema.testData.polymorphism5.{Child51, Child52, Parent5}
-import com.kjetland.jackson.jsonSchema.testData.polymorphism6.{Child61, Parent6}
-import com.kjetland.jackson.jsonSchema.testDataScala._
-import com.kjetland.jackson.jsonSchema.testData_issue_24.EntityWrapper
-import javax.validation.groups.Default
-import org.scalatest.{FunSuite, Matchers}
-
-import scala.collection.JavaConverters._
-
-class JsonSchemaGeneratorTest extends FunSuite with Matchers {
-
- val _objectMapper = new ObjectMapper()
- val _objectMapperScala = new ObjectMapper().registerModule(new DefaultScalaModule)
-
- val _objectMapperKotlin = new ObjectMapper().registerModule(new KotlinModule())
-
- val mixinModule = new MixinModule
-
- List(_objectMapper, _objectMapperScala).foreach {
- om =>
- val simpleModule = new SimpleModule()
- simpleModule.addSerializer(classOf[PojoWithCustomSerializer], new PojoWithCustomSerializerSerializer)
- simpleModule.addDeserializer(classOf[PojoWithCustomSerializer], new PojoWithCustomSerializerDeserializer)
- om.registerModule(simpleModule)
-
- om.registerModule(new JavaTimeModule)
- om.registerModule(new Jdk8Module)
- om.registerModule(new JodaModule)
-
- // For the mixin-test
- om.registerModule(mixinModule)
-
- om.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
- om.setTimeZone(TimeZone.getDefault)
- }
-
-
-
- val jsonSchemaGenerator = new JsonSchemaGenerator(_objectMapper, debug = true)
- val jsonSchemaGeneratorHTML5 = new JsonSchemaGenerator(_objectMapper, debug = true, config = JsonSchemaConfig.html5EnabledSchema)
- val jsonSchemaGeneratorScala = new JsonSchemaGenerator(_objectMapperScala, debug = true)
- val jsonSchemaGeneratorScalaHTML5 = new JsonSchemaGenerator(_objectMapperScala, debug = true, config = JsonSchemaConfig.html5EnabledSchema)
-
- val jsonSchemaGeneratorKotlin = new JsonSchemaGenerator(_objectMapperKotlin, debug = true)
-
- val vanillaJsonSchemaDraft4WithIds = JsonSchemaConfig.vanillaJsonSchemaDraft4.copy(useTypeIdForDefinitionName = true)
- val jsonSchemaGeneratorWithIds = new JsonSchemaGenerator(_objectMapperScala, debug = true, vanillaJsonSchemaDraft4WithIds)
-
- val jsonSchemaGeneratorNullable = new JsonSchemaGenerator(_objectMapper, debug = true, config = JsonSchemaConfig.nullableJsonSchemaDraft4)
- val jsonSchemaGeneratorHTML5Nullable = new JsonSchemaGenerator(_objectMapper, debug = true,
- config = JsonSchemaConfig.html5EnabledSchema.copy(useOneOfForNullables = true))
- val jsonSchemaGeneratorWithIdsNullable = new JsonSchemaGenerator(_objectMapperScala, debug = true,
- vanillaJsonSchemaDraft4WithIds.copy(useOneOfForNullables = true))
-
- val jsonSchemaGenerator_draft_06 = new JsonSchemaGenerator(_objectMapper, debug = true,
- JsonSchemaConfig.vanillaJsonSchemaDraft4.withJsonSchemaDraft(JsonSchemaDraft.DRAFT_06))
-
- val jsonSchemaGenerator_draft_07 = new JsonSchemaGenerator(_objectMapper, debug = true,
- JsonSchemaConfig.vanillaJsonSchemaDraft4.withJsonSchemaDraft(JsonSchemaDraft.DRAFT_07))
-
- val jsonSchemaGenerator_draft_2019_09 = new JsonSchemaGenerator(_objectMapper, debug = true,
- JsonSchemaConfig.vanillaJsonSchemaDraft4.withJsonSchemaDraft(JsonSchemaDraft.DRAFT_2019_09))
-
- val testData = new TestData{}
-
- def asPrettyJson(node:JsonNode, om:ObjectMapper):String = {
- om.writerWithDefaultPrettyPrinter().writeValueAsString(node)
- }
-
-
- // Asserts that we're able to go from object => json => equal object
- def assertToFromJson(g:JsonSchemaGenerator, o:Any): JsonNode = {
- assertToFromJson(g, o, o.getClass)
- }
-
- // Asserts that we're able to go from object => json => equal object
- // desiredType might be a class which o extends (polymorphism)
- def assertToFromJson(g:JsonSchemaGenerator, o:Any, desiredType:Class[_]): JsonNode = {
- val json = g.rootObjectMapper.writeValueAsString(o)
- println(s"json: $json")
- val jsonNode = g.rootObjectMapper.readTree(json)
- val r = g.rootObjectMapper.treeToValue(jsonNode, desiredType)
- assert(o == r)
- jsonNode
- }
-
- def useSchema(jsonSchema:JsonNode, jsonToTestAgainstSchema:Option[JsonNode] = None): Unit = {
- val schemaValidator = JsonSchemaFactory.byDefault().getJsonSchema(jsonSchema)
- jsonToTestAgainstSchema.foreach {
- node =>
- val r = schemaValidator.validate(node)
- if (!r.isSuccess) {
- throw new Exception("json does not validate against schema: " + r)
- }
-
- }
- }
-
- // Generates schema, validates the schema using external schema validator and
- // Optionally tries to validate json against the schema.
- def generateAndValidateSchema
- (
- g:JsonSchemaGenerator,
- clazz:Class[_], jsonToTestAgainstSchema:Option[JsonNode] = None,
- jsonSchemaDraft: JsonSchemaDraft = JsonSchemaDraft.DRAFT_04
- ):JsonNode = {
- val schema = g.generateJsonSchema(clazz)
-
- println("--------------------------------------------")
- println(asPrettyJson(schema, g.rootObjectMapper))
-
- assert(jsonSchemaDraft.url == schema.at("/$schema").asText())
-
- useSchema(schema, jsonToTestAgainstSchema)
-
- schema
- }
-
- // Generates schema, validates the schema using external schema validator and
- // Optionally tries to validate json against the schema.
- def generateAndValidateSchemaUsingJavaType
- (
- g:JsonSchemaGenerator,
- javaType:JavaType,
- jsonToTestAgainstSchema:Option[JsonNode] = None,
- jsonSchemaDraft: JsonSchemaDraft = JsonSchemaDraft.DRAFT_04
- ):JsonNode = {
- val schema = g.generateJsonSchema(javaType)
-
- println("--------------------------------------------")
- println(asPrettyJson(schema, g.rootObjectMapper))
-
- assert(jsonSchemaDraft.url == schema.at("/$schema").asText())
-
- useSchema(schema, jsonToTestAgainstSchema)
-
- schema
- }
-
- def assertJsonSubTypesInfo(node:JsonNode, typeParamName:String, typeName:String, html5Checks:Boolean = false): Unit ={
- /*
- "properties" : {
- "type" : {
- "type" : "string",
- "enum" : [ "child1" ],
- "default" : "child1"
- },
- },
- "title" : "child1",
- "required" : [ "type" ]
- */
- assert(node.at(s"/properties/$typeParamName/type").asText() == "string")
- assert(node.at(s"/properties/$typeParamName/enum/0").asText() == typeName)
- assert(node.at(s"/properties/$typeParamName/default").asText() == typeName)
- assert(node.at(s"/title").asText() == typeName)
- assertPropertyRequired(node, typeParamName, required = true)
-
- if (html5Checks) {
- assert(node.at(s"/properties/$typeParamName/options/hidden").asBoolean())
- assert(node.at(s"/options/multiple_editor_select_via_property/property").asText() == typeParamName)
- assert(node.at(s"/options/multiple_editor_select_via_property/value").asText() == typeName)
- } else {
- assert(node.at(s"/options/multiple_editor_select_via_property/property").isInstanceOf[MissingNode])
-
- }
- }
-
- def getArrayNodeAsListOfStrings(node:JsonNode):List[String] = {
- node match {
- case _:MissingNode => List()
- case x:ArrayNode => x.asScala.toList.map(_.asText())
- }
- }
-
- def getRequiredList(node:JsonNode):List[String] = {
- getArrayNodeAsListOfStrings(node.at(s"/required"))
- }
-
- def assertPropertyRequired(schema:JsonNode, propertyName:String, required:Boolean): Unit = {
- if (required) {
- assert(getRequiredList(schema).contains(propertyName))
- } else {
- assert(!getRequiredList(schema).contains(propertyName))
- }
- }
-
- def getNodeViaRefs(root:JsonNode, pathToArrayOfRefs:String, definitionName:String):JsonNode = {
- val arrayItemNodes:List[JsonNode] = root.at(pathToArrayOfRefs) match {
- case arrayNode:ArrayNode => arrayNode.iterator().asScala.toList
- case objectNode:ObjectNode => List(objectNode)
-
- }
- val ref = arrayItemNodes.map(_.get("$ref").asText()).find(_.endsWith(s"/$definitionName")).get
- // use ref to look the node up
- val fixedRef = ref.substring(1) // Removing starting #
- root.at(fixedRef)
- }
-
- def getNodeViaRefs(root:JsonNode, nodeWithRef:JsonNode, definitionName:String):ObjectNode = {
- val ref = nodeWithRef.at("/$ref").asText()
- assert(ref.endsWith(s"/$definitionName"))
- // use ref to look the node up
- val fixedRef = ref.substring(1) // Removing starting #
- root.at(fixedRef).asInstanceOf[ObjectNode]
- }
-
- test("Generate scheme for plain class not using @JsonTypeInfo") {
-
- val enumList = MyEnum.values().toList.map(_.toString)
-
- {
- val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.classNotExtendingAnything)
- val schema = generateAndValidateSchema(jsonSchemaGenerator, testData.classNotExtendingAnything.getClass, Some(jsonNode))
-
- assert(!schema.at("/additionalProperties").asBoolean())
- assert(schema.at("/properties/someString/type").asText() == "string")
-
- assert(schema.at("/properties/myEnum/type").asText() == "string")
- assert(getArrayNodeAsListOfStrings(schema.at("/properties/myEnum/enum")) == enumList)
- }
-
- {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorNullable, testData.classNotExtendingAnything)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorNullable, testData.classNotExtendingAnything.getClass, Some(jsonNode))
-
- assert(!schema.at("/additionalProperties").asBoolean())
- assertNullableType(schema, "/properties/someString", "string")
-
- assertNullableType(schema, "/properties/myEnum", "string")
- assert(getArrayNodeAsListOfStrings(schema.at("/properties/myEnum/oneOf/1/enum")) == enumList)
- }
-
- {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorScala, testData.classNotExtendingAnythingScala)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorScala, testData.classNotExtendingAnythingScala.getClass, Some(jsonNode))
-
- assert(!schema.at("/additionalProperties").asBoolean())
- assert(schema.at("/properties/someString/type").asText() == "string")
-
- assert(schema.at("/properties/myEnum/type").asText() == "string")
- assert(getArrayNodeAsListOfStrings(schema.at("/properties/myEnum/enum")) == enumList)
- assert(getArrayNodeAsListOfStrings(schema.at("/properties/myEnumO/enum")) == enumList)
- }
- {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorScala, testData.genericClassVoid)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorScala, testData.genericClassVoid.getClass, Some(jsonNode))
- assert(schema.at("/type").asText() == "object")
- assert(!schema.at("/additionalProperties").asBoolean())
- assert(schema.at("/properties/content/type").asText() == "null")
- assert(schema.at("/properties/list/type").asText() == "array")
- assert(schema.at("/properties/list/items/type").asText() == "null")
- }
- {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorScala, testData.genericMapLike)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorScala, testData.genericMapLike.getClass, Some(jsonNode))
- assert(schema.at("/type").asText() == "object")
- assert(schema.at("/additionalProperties/type").asText() == "string")
- }
- }
-
- test("Generating schema for concrete class which happens to extend class using @JsonTypeInfo") {
-
- def doTest(pojo:Object, clazz:Class[_], g:JsonSchemaGenerator): Unit = {
- val jsonNode = assertToFromJson(g, pojo)
- val schema = generateAndValidateSchema(g, clazz, Some(jsonNode))
-
- assert(!schema.at("/additionalProperties").asBoolean())
- assert(schema.at("/properties/parentString/type").asText() == "string")
- assertJsonSubTypesInfo(schema, "type", "child1")
- }
-
- doTest(testData.child1, testData.child1.getClass, jsonSchemaGenerator)
- doTest(testData.child1Scala, testData.child1Scala.getClass, jsonSchemaGeneratorScala)
- }
-
- test("Generate schema for regular class which has a property of class annotated with @JsonTypeInfo") {
-
- def assertDefaultValues(schema:JsonNode): Unit ={
- assert(schema.at("/properties/stringWithDefault/type").asText() == "string")
- assert(schema.at("/properties/stringWithDefault/default").asText() == "x")
- assert(schema.at("/properties/intWithDefault/type").asText() == "integer")
- assert(schema.at("/properties/intWithDefault/default").asInt() == 12)
- assert(schema.at("/properties/booleanWithDefault/type").asText() == "boolean")
- assert(schema.at("/properties/booleanWithDefault/default").asBoolean())
- }
-
- def assertNullableDefaultValues(schema:JsonNode): Unit = {
- assert(schema.at("/properties/stringWithDefault/oneOf/0/type").asText() == "null")
- assert(schema.at("/properties/stringWithDefault/oneOf/0/title").asText() == "Not included")
- assert(schema.at("/properties/stringWithDefault/oneOf/1/type").asText() == "string")
- assert(schema.at("/properties/stringWithDefault/oneOf/1/default").asText() == "x")
-
- assert(schema.at("/properties/intWithDefault/type").asText() == "integer")
- assert(schema.at("/properties/intWithDefault/default").asInt() == 12)
- assert(schema.at("/properties/booleanWithDefault/type").asText() == "boolean")
- assert(schema.at("/properties/booleanWithDefault/default").asBoolean())
- }
-
- // Java
- {
- val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.pojoWithParent)
- val schema = generateAndValidateSchema(jsonSchemaGenerator, testData.pojoWithParent.getClass, Some(jsonNode))
-
- assert(!schema.at("/additionalProperties").asBoolean())
- assert(schema.at("/properties/pojoValue/type").asText() == "boolean")
- assertDefaultValues(schema)
-
- assertChild1(schema, "/properties/child/oneOf")
- assertChild2(schema, "/properties/child/oneOf")
- }
-
- // Java - html5
- {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorHTML5, testData.pojoWithParent)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorHTML5, testData.pojoWithParent.getClass, Some(jsonNode))
-
- assert(!schema.at("/additionalProperties").asBoolean())
- assert(schema.at("/properties/pojoValue/type").asText() == "boolean")
- assertDefaultValues(schema)
-
- assertChild1(schema, "/properties/child/oneOf", html5Checks = true)
- assertChild2(schema, "/properties/child/oneOf", html5Checks = true)
- }
-
- // Java - html5/nullable
- {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorHTML5Nullable, testData.pojoWithParent)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorHTML5Nullable, testData.pojoWithParent.getClass, Some(jsonNode))
-
- assert(!schema.at("/additionalProperties").asBoolean())
- assertNullableType(schema, "/properties/pojoValue", "boolean")
- assertNullableDefaultValues(schema)
-
- assertNullableChild1(schema, "/properties/child/oneOf/1/oneOf", html5Checks = true)
- assertNullableChild2(schema, "/properties/child/oneOf/1/oneOf", html5Checks = true)
- }
-
- //Using fully-qualified class names
- {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorWithIds, testData.pojoWithParent)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorWithIds, testData.pojoWithParent.getClass, Some(jsonNode))
-
- assert(!schema.at("/additionalProperties").asBoolean())
- assert(schema.at("/properties/pojoValue/type").asText() == "boolean")
- assertDefaultValues(schema)
-
- assertChild1(schema, "/properties/child/oneOf", "com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child1")
- assertChild2(schema, "/properties/child/oneOf", "com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child2")
- }
-
- // Using fully-qualified class names and nullable types
- {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorWithIdsNullable, testData.pojoWithParent)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorWithIdsNullable, testData.pojoWithParent.getClass, Some(jsonNode))
-
- assert(!schema.at("/additionalProperties").asBoolean())
- assertNullableType(schema, "/properties/pojoValue", "boolean")
- assertNullableDefaultValues(schema)
-
- assertNullableChild1(schema, "/properties/child/oneOf/1/oneOf", "com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child1")
- assertNullableChild2(schema, "/properties/child/oneOf/1/oneOf", "com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child2")
- }
-
- // Scala
- {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorScala, testData.pojoWithParentScala)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorScala, testData.pojoWithParentScala.getClass, Some(jsonNode))
-
- assert(!schema.at("/additionalProperties").asBoolean())
- assert(schema.at("/properties/pojoValue/type").asText() == "boolean")
- assertDefaultValues(schema)
-
- assertChild1(schema, "/properties/child/oneOf", "Child1Scala")
- assertChild2(schema, "/properties/child/oneOf", "Child2Scala")
- }
- }
-
- def assertChild1(node:JsonNode, path:String, defName:String = "Child1", typeParamName:String = "type", typeName:String = "child1", html5Checks:Boolean = false): Unit ={
- val child1 = getNodeViaRefs(node, path, defName)
- assertJsonSubTypesInfo(child1, typeParamName, typeName, html5Checks)
- assert(child1.at("/properties/parentString/type").asText() == "string")
- assert(child1.at("/properties/child1String/type").asText() == "string")
- assert(child1.at("/properties/_child1String2/type").asText() == "string")
- assert(child1.at("/properties/_child1String3/type").asText() == "string")
- assertPropertyRequired(child1, "_child1String3", required = true)
- }
-
- def assertNullableChild1(node:JsonNode, path:String, defName:String = "Child1", html5Checks:Boolean = false): Unit ={
- val child1 = getNodeViaRefs(node, path, defName)
- assertJsonSubTypesInfo(child1, "type", "child1", html5Checks)
- assertNullableType(child1, "/properties/parentString", "string")
- assertNullableType(child1, "/properties/child1String", "string")
- assertNullableType(child1, "/properties/_child1String2", "string")
- assert(child1.at("/properties/_child1String3/type").asText() == "string")
- assertPropertyRequired(child1, "_child1String3", required = true)
- }
-
- def assertChild2(node:JsonNode, path:String, defName:String = "Child2", typeParamName:String = "type", typeName:String = "child2", html5Checks:Boolean = false): Unit ={
- val child2 = getNodeViaRefs(node, path, defName)
- assertJsonSubTypesInfo(child2, typeParamName, typeName, html5Checks)
- assert(child2.at("/properties/parentString/type").asText() == "string")
- assert(child2.at("/properties/child2int/type").asText() == "integer")
- }
-
- def assertNullableChild2(node:JsonNode, path:String, defName:String = "Child2", html5Checks:Boolean = false): Unit = {
- val child2 = getNodeViaRefs(node, path, defName)
- assertJsonSubTypesInfo(child2, "type", "child2", html5Checks)
- assertNullableType(child2, "/properties/parentString", "string")
- assertNullableType(child2, "/properties/child2int", "integer")
- }
-
- def assertNullableType(node:JsonNode, path:String, expectedType:String): Unit = {
- val nullType = node.at(path).at("/oneOf/0")
- assert(nullType.at("/type").asText() == "null")
- assert(nullType.at("/title").asText() == "Not included")
-
- val valueType = node.at(path).at("/oneOf/1")
- assert(valueType.at("/type").asText() == expectedType)
-
- Option(getRequiredList(node)).map(xs => assert(!xs.contains(path.split('/').last)))
- }
-
- test("Generate schema for super class annotated with @JsonTypeInfo - use = JsonTypeInfo.Id.NAME") {
-
- // Java
- {
- val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.child1)
- assertToFromJson(jsonSchemaGenerator, testData.child1, classOf[Parent])
-
- val schema = generateAndValidateSchema(jsonSchemaGenerator, classOf[Parent], Some(jsonNode))
-
- assertChild1(schema, "/oneOf")
- assertChild2(schema, "/oneOf")
- }
-
- // Java + Nullables
- {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorNullable, testData.child1)
- assertToFromJson(jsonSchemaGeneratorNullable, testData.child1, classOf[Parent])
-
- val schema = generateAndValidateSchema(jsonSchemaGenerator, classOf[Parent], Some(jsonNode))
-
- assertChild1(schema, "/oneOf")
- assertChild2(schema, "/oneOf")
- }
-
- // Scala
- {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorScala, testData.child1Scala)
- assertToFromJson(jsonSchemaGeneratorScala, testData.child1Scala, classOf[ParentScala])
-
- val schema = generateAndValidateSchema(jsonSchemaGeneratorScala, classOf[ParentScala], Some(jsonNode))
-
- assertChild1(schema, "/oneOf", "Child1Scala")
- assertChild2(schema, "/oneOf", "Child2Scala")
- }
-
- }
-
- test("Generate schema for super class annotated with @JsonTypeInfo - use = JsonTypeInfo.Id.CLASS") {
-
- // Java
- {
-
- val config = JsonSchemaConfig.vanillaJsonSchemaDraft4
- val g = new JsonSchemaGenerator(_objectMapper, debug = true, config)
-
- val jsonNode = assertToFromJson(g, testData.child21)
- assertToFromJson(g, testData.child21, classOf[Parent2])
-
- val schema = generateAndValidateSchema(g, classOf[Parent2], Some(jsonNode))
-
- assertChild1(schema, "/oneOf", "Child21", typeParamName = "clazz", typeName = "com.kjetland.jackson.jsonSchema.testData.polymorphism2.Child21")
- assertChild2(schema, "/oneOf", "Child22", typeParamName = "clazz", typeName = "com.kjetland.jackson.jsonSchema.testData.polymorphism2.Child22")
- }
- }
-
- test("Generate schema for super class annotated with @JsonTypeInfo - use = JsonTypeInfo.Id.MINIMAL_CLASS") {
-
- // Java
- {
-
- val config = JsonSchemaConfig.vanillaJsonSchemaDraft4
- val g = new JsonSchemaGenerator(_objectMapper, debug = true, config)
-
- val jsonNode = assertToFromJson(g, testData.child51)
- assertToFromJson(g, testData.child51, classOf[Parent5])
-
- val schema = generateAndValidateSchema(g, classOf[Parent5], Some(jsonNode))
-
- assertChild1(schema, "/oneOf", "Child51", typeParamName = "clazz", typeName = ".Child51")
- assertChild2(schema, "/oneOf", "Child52", typeParamName = "clazz", typeName = ".Child52")
-
- val embeddedTypeName = _objectMapper.valueToTree[ObjectNode](new Parent5.Child51InnerClass()).get("clazz").asText()
- assertChild1(schema, "/oneOf", "Child51InnerClass", typeParamName = "clazz", typeName = embeddedTypeName)
- }
- }
-
- test("Generate schema for interface annotated with @JsonTypeInfo - use = JsonTypeInfo.Id.MINIMAL_CLASS") {
-
- // Java
- {
- val config = JsonSchemaConfig.vanillaJsonSchemaDraft4
- val g = new JsonSchemaGenerator(_objectMapper, debug = true, config)
-
- val jsonNode = assertToFromJson(g, testData.child61)
- assertToFromJson(g, testData.child61, classOf[Parent6])
-
- val schema = generateAndValidateSchema(g, classOf[Parent6], Some(jsonNode))
-
- assertChild1(schema, "/oneOf", "Child61", typeParamName = "clazz", typeName = ".Child61")
- assertChild2(schema, "/oneOf", "Child62", typeParamName = "clazz", typeName = ".Child62")
- }
- }
-
-
- test("Generate schema for super class annotated with @JsonTypeInfo - include = JsonTypeInfo.As.EXISTING_PROPERTY") {
-
- // Java
- {
- val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.child31)
- assertToFromJson(jsonSchemaGenerator, testData.child31, classOf[Parent3])
-
- val schema = generateAndValidateSchema(jsonSchemaGenerator, classOf[Parent3], Some(jsonNode))
-
- assertChild1(schema, "/oneOf", "Child31", typeName = "child31")
- assertChild2(schema, "/oneOf", "Child32", typeName = "child32")
- }
- }
-
- test("Generate schema for super class annotated with @JsonTypeInfo - include = JsonTypeInfo.As.CUSTOM") {
-
- // Java
- {
-
- val jsonNode1 = assertToFromJson(jsonSchemaGenerator, testData.child41)
- val jsonNode2 = assertToFromJson(jsonSchemaGenerator, testData.child42)
-
- val schema1 = generateAndValidateSchema(jsonSchemaGenerator, classOf[Child41], Some(jsonNode1))
- val schema2 = generateAndValidateSchema(jsonSchemaGenerator, classOf[Child42], Some(jsonNode2))
-
- assertJsonSubTypesInfo(schema1, "type", "Child41")
- assertJsonSubTypesInfo(schema2, "type", "Child42")
- }
- }
-
- test("Generate schema for class containing generics with same base type but different type arguments") {
- {
- val config = JsonSchemaConfig.vanillaJsonSchemaDraft4
- val g = new JsonSchemaGenerator(_objectMapper, debug = true, config)
-
- val instance = new GenericClassContainer()
- val jsonNode = assertToFromJson(g, instance)
- assertToFromJson(g, instance, classOf[GenericClassContainer])
-
- val schema = generateAndValidateSchema(g, classOf[GenericClassContainer], Some(jsonNode))
-
- assert(schema.at("/definitions/BoringClass/properties/data/type").asText() == "integer")
- assert(schema.at("/definitions/GenericClass(String)/properties/data/type").asText() == "string")
- assert(schema.at("/definitions/GenericWithJsonTypeName(String)/properties/data/type").asText() == "string")
- assert(schema.at("/definitions/GenericClass(BoringClass)/properties/data/$ref").asText() == "#/definitions/BoringClass")
- assert(schema.at("/definitions/GenericClassTwo(String,GenericClass(BoringClass))/properties/data1/type").asText() == "string")
- assert(schema.at("/definitions/GenericClassTwo(String,GenericClass(BoringClass))/properties/data2/$ref").asText() == "#/definitions/GenericClass(BoringClass)")
- }
- }
-
- test("additionalProperties / failOnUnknownProperties") {
-
- // Test default
- {
- val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.manyPrimitives)
- val schema = generateAndValidateSchema(jsonSchemaGenerator, testData.manyPrimitives.getClass, Some(jsonNode))
-
- assert(schema.at("/additionalProperties").asBoolean() == false)
- }
-
- // Test turning failOnUnknownProperties off
- {
- val generator = new JsonSchemaGenerator(_objectMapper, debug = false,
- config = JsonSchemaConfig.vanillaJsonSchemaDraft4.copy(failOnUnknownProperties = false)
- )
- val jsonNode = assertToFromJson(generator, testData.manyPrimitives)
- val schema = generateAndValidateSchema(generator, testData.manyPrimitives.getClass, Some(jsonNode))
-
- assert(schema.at("/additionalProperties").asBoolean() == true)
- }
- }
-
- test("primitives") {
-
- // Java
- {
- val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.manyPrimitives)
- val schema = generateAndValidateSchema(jsonSchemaGenerator, testData.manyPrimitives.getClass, Some(jsonNode))
-
- assert(schema.at("/properties/_string/type").asText() == "string")
-
- assert(schema.at("/properties/_integer/type").asText() == "integer")
- assertPropertyRequired(schema, "_integer", required = false) // Should allow null by default
-
- assert(schema.at("/properties/_int/type").asText() == "integer")
- assertPropertyRequired(schema, "_int", required = true) // Must have a value
-
- assert(schema.at("/properties/_booleanObject/type").asText() == "boolean")
- assertPropertyRequired(schema, "_booleanObject", required = false) // Should allow null by default
-
- assert(schema.at("/properties/_booleanPrimitive/type").asText() == "boolean")
- assertPropertyRequired(schema, "_booleanPrimitive", required = true) // Must be required since it must have true or false - not null
-
- assert(schema.at("/properties/_booleanObjectWithNotNull/type").asText() == "boolean")
- assertPropertyRequired(schema, "_booleanObjectWithNotNull", required = true)
-
- assert(schema.at("/properties/_doubleObject/type").asText() == "number")
- assertPropertyRequired(schema, "_doubleObject", required = false)// Should allow null by default
-
- assert(schema.at("/properties/_doublePrimitive/type").asText() == "number")
- assertPropertyRequired(schema, "_doublePrimitive", required = true) // Must be required since it must have a value - not null
-
- assert(schema.at("/properties/myEnum/type").asText() == "string")
- assert(getArrayNodeAsListOfStrings(schema.at("/properties/myEnum/enum")) == MyEnum.values().toList.map(_.toString))
- assert(schema.at("/properties/myEnum/JsonSchemaInjectOnEnum").asText() == "true")
- }
-
- // Java with nullable types
- {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorNullable, testData.manyPrimitivesNulls)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorNullable, testData.manyPrimitivesNulls.getClass, Some(jsonNode))
-
- assertNullableType(schema, "/properties/_string", "string")
- assertNullableType(schema, "/properties/_integer", "integer")
- assertNullableType(schema, "/properties/_booleanObject", "boolean")
- assertNullableType(schema, "/properties/_doubleObject", "number")
-
- // We're actually going to test this elsewhere, because if we set this to null here it'll break the "generateAndValidateSchema"
- // test. What's fun is that the type system will allow you to set the value as null, but the schema won't (because there's a @NotNull annotation on it).
- assert(schema.at("/properties/_booleanObjectWithNotNull/type").asText() == "boolean")
- assertPropertyRequired(schema, "_booleanObjectWithNotNull", required = true)
-
- assert(schema.at("/properties/_int/type").asText() == "integer")
- assertPropertyRequired(schema, "_int", required = true)
-
- assert(schema.at("/properties/_booleanPrimitive/type").asText() == "boolean")
- assertPropertyRequired(schema, "_booleanPrimitive", required = true)
-
- assert(schema.at("/properties/_doublePrimitive/type").asText() == "number")
- assertPropertyRequired(schema, "_doublePrimitive", required = true)
-
- assertNullableType(schema, "/properties/myEnum", "string")
- assert(getArrayNodeAsListOfStrings(schema.at("/properties/myEnum/oneOf/1/enum")) == MyEnum.values().toList.map(_.toString))
- }
-
- // Scala
- {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorScala, testData.manyPrimitivesScala)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorScala, testData.manyPrimitivesScala.getClass, Some(jsonNode))
-
- assert(schema.at("/properties/_string/type").asText() == "string")
-
- assert(schema.at("/properties/_integer/type").asText() == "integer")
- assertPropertyRequired(schema, "_integer", required = true) // Should allow null by default
-
- assert(schema.at("/properties/_boolean/type").asText() == "boolean")
- assertPropertyRequired(schema, "_boolean", required = true) // Should allow null by default
-
- assert(schema.at("/properties/_double/type").asText() == "number")
- assertPropertyRequired(schema, "_double", required = true) // Should allow null by default
- }
- }
-
- test("scala using option") {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorScala, testData.pojoUsingOptionScala)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorScala, testData.pojoUsingOptionScala.getClass, Some(jsonNode))
-
- assert(schema.at("/properties/_string/type").asText() == "string")
- assertPropertyRequired(schema, "_string", required = false) // Should allow null by default
-
- assert(schema.at("/properties/_integer/type").asText() == "integer")
- assertPropertyRequired(schema, "_integer", required = false) // Should allow null by default
-
- assert(schema.at("/properties/_boolean/type").asText() == "boolean")
- assertPropertyRequired(schema, "_boolean", required = false) // Should allow null by default
-
- assert(schema.at("/properties/_double/type").asText() == "number")
- assertPropertyRequired(schema, "_double", required = false) // Should allow null by default
-
- val child1 = getNodeViaRefs(schema, schema.at("/properties/child1"), "Child1Scala")
-
- assertJsonSubTypesInfo(child1, "type", "child1")
- assert(child1.at("/properties/parentString/type").asText() == "string")
- assert(child1.at("/properties/child1String/type").asText() == "string")
- assert(child1.at("/properties/_child1String2/type").asText() == "string")
- assert(child1.at("/properties/_child1String3/type").asText() == "string")
-
- assert(schema.at("/properties/optionalList/type").asText() == "array")
- assert(schema.at("/properties/optionalList/items/$ref").asText() == "#/definitions/ClassNotExtendingAnythingScala")
- }
-
- test("java using option") {
- val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.pojoUsingOptionalJava)
- val schema = generateAndValidateSchema(jsonSchemaGenerator, testData.pojoUsingOptionalJava.getClass, Some(jsonNode))
-
- assert(schema.at("/properties/_string/type").asText() == "string")
- assertPropertyRequired(schema, "_string", required = false) // Should allow null by default
-
- assert(schema.at("/properties/_integer/type").asText() == "integer")
- assertPropertyRequired(schema, "_integer", required = false) // Should allow null by default
-
- val child1 = getNodeViaRefs(schema, schema.at("/properties/child1"), "Child1")
-
- assertJsonSubTypesInfo(child1, "type", "child1")
- assert(child1.at("/properties/parentString/type").asText() == "string")
- assert(child1.at("/properties/child1String/type").asText() == "string")
- assert(child1.at("/properties/_child1String2/type").asText() == "string")
- assert(child1.at("/properties/_child1String3/type").asText() == "string")
-
- assert(schema.at("/properties/optionalList/type").asText() == "array")
- assert(schema.at("/properties/optionalList/items/$ref").asText() == "#/definitions/ClassNotExtendingAnything")
- }
-
- test("nullable Java using option") {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorNullable, testData.pojoUsingOptionalJava)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorNullable, testData.pojoUsingOptionalJava.getClass, Some(jsonNode))
-
- assertNullableType(schema, "/properties/_string", "string")
- assertNullableType(schema, "/properties/_integer", "integer")
-
- val child1 = getNodeViaRefs(schema, schema.at("/properties/child1/oneOf/1"), "Child1")
-
- assertJsonSubTypesInfo(child1, "type", "child1")
- assertNullableType(child1, "/properties/parentString", "string")
- assertNullableType(child1, "/properties/child1String", "string")
- assertNullableType(child1, "/properties/_child1String2", "string")
- assert(child1.at("/properties/_child1String3/type").asText() == "string")
-
- assertNullableType(schema, "/properties/optionalList", "array")
- assert(schema.at("/properties/optionalList/oneOf/1/items/$ref").asText() == "#/definitions/ClassNotExtendingAnything")
- }
-
- test("custom serializer not overriding JsonSerializer.acceptJsonFormatVisitor") {
- val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.pojoWithCustomSerializer)
- val schema = generateAndValidateSchema(jsonSchemaGenerator, testData.pojoWithCustomSerializer.getClass, Some(jsonNode))
- assert(schema.asInstanceOf[ObjectNode].fieldNames().asScala.toList == List("$schema", "title")) // Empty schema due to custom serializer
- }
-
- test("object with property using custom serializer not overriding JsonSerializer.acceptJsonFormatVisitor") {
- val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.objectWithPropertyWithCustomSerializer)
- val schema = generateAndValidateSchema(jsonSchemaGenerator, testData.objectWithPropertyWithCustomSerializer.getClass, Some(jsonNode))
- assert(schema.at("/properties/s/type").asText() == "string")
- assert(schema.at("/properties/child").asInstanceOf[ObjectNode].fieldNames().asScala.toList == List())
- }
-
- test("pojoWithArrays") {
-
- def doTest(pojo:Object, clazz:Class[_], g:JsonSchemaGenerator, html5Checks:Boolean): Unit ={
-
- val jsonNode = assertToFromJson(g, pojo)
- val schema = generateAndValidateSchema(g, clazz, Some(jsonNode))
-
- assert(schema.at("/properties/intArray1/type").asText() == "array")
- assert(schema.at("/properties/intArray1/items/type").asText() == "integer")
-
- assert(schema.at("/properties/stringArray/type").asText() == "array")
- assert(schema.at("/properties/stringArray/items/type").asText() == "string")
-
- assert(schema.at("/properties/stringList/type").asText() == "array")
- assert(schema.at("/properties/stringList/items/type").asText() == "string")
- assert(schema.at("/properties/stringList/minItems").asInt() == 1)
- assert(schema.at("/properties/stringList/maxItems").asInt() == 10)
-
- assert(schema.at("/properties/polymorphismList/type").asText() == "array")
- assertChild1(schema, "/properties/polymorphismList/items/oneOf", html5Checks = html5Checks)
- assertChild2(schema, "/properties/polymorphismList/items/oneOf", html5Checks = html5Checks)
-
- assert(schema.at("/properties/polymorphismArray/type").asText() == "array")
- assertChild1(schema, "/properties/polymorphismArray/items/oneOf", html5Checks = html5Checks)
- assertChild2(schema, "/properties/polymorphismArray/items/oneOf", html5Checks = html5Checks)
-
- assert(schema.at("/properties/listOfListOfStrings/type").asText() == "array")
- assert(schema.at("/properties/listOfListOfStrings/items/type").asText() == "array")
- assert(schema.at("/properties/listOfListOfStrings/items/items/type").asText() == "string")
-
- assert(schema.at("/properties/setOfUniqueValues/type").asText() == "array")
- assert(schema.at("/properties/setOfUniqueValues/items/type").asText() == "string")
-
- if (html5Checks) {
- assert(schema.at("/properties/setOfUniqueValues/uniqueItems").asText() == "true")
- assert(schema.at("/properties/setOfUniqueValues/format").asText() == "checkbox")
- }
- }
-
- doTest(testData.pojoWithArrays, testData.pojoWithArrays.getClass, jsonSchemaGenerator, html5Checks = false)
- doTest(testData.pojoWithArraysScala, testData.pojoWithArraysScala.getClass, jsonSchemaGeneratorScala, html5Checks = false)
- doTest(testData.pojoWithArraysScala, testData.pojoWithArraysScala.getClass, jsonSchemaGeneratorScalaHTML5, html5Checks = true)
- doTest(testData.pojoWithArrays, testData.pojoWithArrays.getClass, jsonSchemaGeneratorScalaHTML5, html5Checks = true)
- }
-
- test("pojoWithArraysNullable") {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorNullable, testData.pojoWithArraysNullable)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorNullable, testData.pojoWithArraysNullable.getClass, Some(jsonNode))
-
- assertNullableType(schema, "/properties/intArray1", "array")
- assert(schema.at("/properties/intArray1/oneOf/1/items/type").asText() == "integer")
-
- assertNullableType(schema, "/properties/stringArray", "array")
- assert(schema.at("/properties/stringArray/oneOf/1/items/type").asText() == "string")
-
- assertNullableType(schema, "/properties/stringList", "array")
- assert(schema.at("/properties/stringList/oneOf/1/items/type").asText() == "string")
-
- assertNullableType(schema, "/properties/polymorphismList", "array")
- assertNullableChild1(schema, "/properties/polymorphismList/oneOf/1/items/oneOf")
- assertNullableChild2(schema, "/properties/polymorphismList/oneOf/1/items/oneOf")
-
- assertNullableType(schema, "/properties/polymorphismArray", "array")
- assertNullableChild1(schema, "/properties/polymorphismArray/oneOf/1/items/oneOf")
- assertNullableChild2(schema, "/properties/polymorphismArray/oneOf/1/items/oneOf")
-
- assertNullableType(schema, "/properties/listOfListOfStrings", "array")
- assert(schema.at("/properties/listOfListOfStrings/oneOf/1/items/type").asText() == "array")
- assert(schema.at("/properties/listOfListOfStrings/oneOf/1/items/items/type").asText() == "string")
- }
-
- test("recursivePojo") {
- // Non-nullable Java types
- {
- val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.recursivePojo)
- val schema = generateAndValidateSchema(jsonSchemaGenerator, testData.recursivePojo.getClass, Some(jsonNode))
-
- assert(schema.at("/properties/myText/type").asText() == "string")
-
- assert(schema.at("/properties/children/type").asText() == "array")
- val defViaRef = getNodeViaRefs(schema, schema.at("/properties/children/items"), "RecursivePojo")
-
- assert(defViaRef.at("/properties/myText/type").asText() == "string")
- assert(defViaRef.at("/properties/children/type").asText() == "array")
- val defViaRef2 = getNodeViaRefs(schema, defViaRef.at("/properties/children/items"), "RecursivePojo")
-
- assert(defViaRef == defViaRef2)
- }
-
- // Nullable Java types
- {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorNullable, testData.recursivePojo)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorNullable, testData.recursivePojo.getClass, Some(jsonNode))
-
- assertNullableType(schema, "/properties/myText", "string")
-
- assertNullableType(schema, "/properties/children", "array")
- val defViaRef = getNodeViaRefs(schema, schema.at("/properties/children/oneOf/1/items"), "RecursivePojo")
-
- assertNullableType(defViaRef, "/properties/myText", "string")
- assertNullableType(defViaRef, "/properties/children", "array")
- val defViaRef2 = getNodeViaRefs(schema, defViaRef.at("/properties/children/oneOf/1/items"), "RecursivePojo")
-
- assert(defViaRef == defViaRef2)
- }
- }
-
- test("pojo using Maps") {
- // Use our standard Java validator
- {
- val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.pojoUsingMaps)
- val schema = generateAndValidateSchema(jsonSchemaGenerator, testData.pojoUsingMaps.getClass, Some(jsonNode))
-
- assert(schema.at("/properties/string2Integer/type").asText() == "object")
- assert(schema.at("/properties/string2Integer/additionalProperties/type").asText() == "integer")
-
- assert(schema.at("/properties/string2String/type").asText() == "object")
- assert(schema.at("/properties/string2String/additionalProperties/type").asText() == "string")
-
- assert(schema.at("/properties/string2PojoUsingJsonTypeInfo/type").asText() == "object")
- assert(schema.at("/properties/string2PojoUsingJsonTypeInfo/additionalProperties/oneOf/0/$ref").asText() == "#/definitions/Child1")
- assert(schema.at("/properties/string2PojoUsingJsonTypeInfo/additionalProperties/oneOf/1/$ref").asText() == "#/definitions/Child2")
- }
-
- // Try it with nullable types.
- {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorNullable, testData.pojoUsingMaps)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorNullable, testData.pojoUsingMaps.getClass, Some(jsonNode))
-
- assertNullableType(schema, "/properties/string2Integer", "object")
- assert(schema.at("/properties/string2Integer/oneOf/1/additionalProperties/type").asText() == "integer")
-
- assertNullableType(schema, "/properties/string2String", "object")
- assert(schema.at("/properties/string2String/oneOf/1/additionalProperties/type").asText() == "string")
-
- assertNullableType(schema, "/properties/string2PojoUsingJsonTypeInfo", "object")
- assert(schema.at("/properties/string2PojoUsingJsonTypeInfo/oneOf/1/additionalProperties/oneOf/0/$ref").asText() == "#/definitions/Child1")
- assert(schema.at("/properties/string2PojoUsingJsonTypeInfo/oneOf/1/additionalProperties/oneOf/1/$ref").asText() == "#/definitions/Child2")
- }
- }
-
- test("pojo Using Custom Annotations") {
- val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.pojoUsingFormat)
- val schema = generateAndValidateSchema(jsonSchemaGenerator, testData.pojoUsingFormat.getClass, Some(jsonNode))
- val schemaHTML5Date = generateAndValidateSchema(jsonSchemaGeneratorHTML5, testData.pojoUsingFormat.getClass, Some(jsonNode))
- val schemaHTML5DateNullable = generateAndValidateSchema(jsonSchemaGeneratorHTML5Nullable, testData.pojoUsingFormat.getClass, Some(jsonNode))
-
- assert(schema.at("/format").asText() == "grid")
- assert(schema.at("/description").asText() == "This is our pojo")
- assert(schema.at("/title").asText() == "Pojo using format")
-
-
- assert(schema.at("/properties/emailValue/type").asText() == "string")
- assert(schema.at("/properties/emailValue/format").asText() == "email")
- assert(schema.at("/properties/emailValue/description").asText() == "This is our email value")
- assert(schema.at("/properties/emailValue/title").asText() == "Email value")
-
- assert(schema.at("/properties/choice/type").asText() == "boolean")
- assert(schema.at("/properties/choice/format").asText() == "checkbox")
-
- assert(schema.at("/properties/dateTime/type").asText() == "string")
- assert(schema.at("/properties/dateTime/format").asText() == "date-time")
- assert(schema.at("/properties/dateTime/description").asText() == "This is description from @JsonPropertyDescription")
- assert(schemaHTML5Date.at("/properties/dateTime/format").asText() == "datetime")
- assert(schemaHTML5DateNullable.at("/properties/dateTime/oneOf/1/format").asText() == "datetime")
-
-
- assert(schema.at("/properties/dateTimeWithAnnotation/type").asText() == "string")
- assert(schema.at("/properties/dateTimeWithAnnotation/format").asText() == "text")
-
- // Make sure autoGenerated title is correct
- assert(schemaHTML5Date.at("/properties/dateTimeWithAnnotation/title").asText() == "Date Time With Annotation")
- }
-
- test("using JavaType") {
- val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.pojoUsingFormat)
- val schema = generateAndValidateSchemaUsingJavaType( jsonSchemaGenerator, _objectMapper.constructType(testData.pojoUsingFormat.getClass), Some(jsonNode))
-
- assert(schema.at("/format").asText() == "grid")
- assert(schema.at("/description").asText() == "This is our pojo")
- assert(schema.at("/title").asText() == "Pojo using format")
-
-
- assert(schema.at("/properties/emailValue/type").asText() == "string")
- assert(schema.at("/properties/emailValue/format").asText() == "email")
- assert(schema.at("/properties/emailValue/description").asText() == "This is our email value")
- assert(schema.at("/properties/emailValue/title").asText() == "Email value")
-
- assert(schema.at("/properties/choice/type").asText() == "boolean")
- assert(schema.at("/properties/choice/format").asText() == "checkbox")
-
- assert(schema.at("/properties/dateTime/type").asText() == "string")
- assert(schema.at("/properties/dateTime/format").asText() == "date-time")
- assert(schema.at("/properties/dateTime/description").asText() == "This is description from @JsonPropertyDescription")
-
-
- assert(schema.at("/properties/dateTimeWithAnnotation/type").asText() == "string")
- assert(schema.at("/properties/dateTimeWithAnnotation/format").asText() == "text")
-
- }
-
- test("using JavaType with @JsonTypeName") {
- val config = JsonSchemaConfig.vanillaJsonSchemaDraft4
- val g = new JsonSchemaGenerator(_objectMapper, debug = true, config)
-
- val instance = new BoringContainer();
- instance.child1 = new PojoUsingJsonTypeName();
- instance.child1.stringWithDefault = "test";
- val jsonNode = assertToFromJson(g, instance)
- assertToFromJson(g, instance, classOf[BoringContainer])
-
- val schema = generateAndValidateSchema(g, classOf[BoringContainer], Some(jsonNode))
-
- assert(schema.at("/definitions/OtherTypeName/type").asText() == "object");
- }
-
- test("scala using option with HTML5") {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorScalaHTML5, testData.pojoUsingOptionScala)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorScalaHTML5, testData.pojoUsingOptionScala.getClass, Some(jsonNode))
-
- assertNullableType(schema, "/properties/_string", "string")
- assert(schema.at("/properties/_string/title").asText() == "_string")
-
- assertNullableType(schema, "/properties/_integer", "integer")
- assert(schema.at("/properties/_integer/title").asText() == "_integer")
-
- assertNullableType(schema, "/properties/_boolean", "boolean")
- assert(schema.at("/properties/_boolean/title").asText() == "_boolean")
-
- assertNullableType(schema, "/properties/_double", "number")
- assert(schema.at("/properties/_double/title").asText() == "_double")
-
- assert(schema.at("/properties/child1/oneOf/0/type").asText() == "null")
- assert(schema.at("/properties/child1/oneOf/0/title").asText() == "Not included")
- val child1 = getNodeViaRefs(schema, schema.at("/properties/child1/oneOf/1"), "Child1Scala")
- assert(schema.at("/properties/child1/title").asText() == "Child 1")
-
- assertJsonSubTypesInfo(child1, "type", "child1", html5Checks = true)
- assert(child1.at("/properties/parentString/type").asText() == "string")
- assert(child1.at("/properties/child1String/type").asText() == "string")
- assert(child1.at("/properties/_child1String2/type").asText() == "string")
- assert(child1.at("/properties/_child1String3/type").asText() == "string")
-
- assert(schema.at("/properties/optionalList/oneOf/0/type").asText() == "null")
- assert(schema.at("/properties/optionalList/oneOf/0/title").asText() == "Not included")
- assert(schema.at("/properties/optionalList/oneOf/1/type").asText() == "array")
- assert(schema.at("/properties/optionalList/oneOf/1/items/$ref").asText() == "#/definitions/ClassNotExtendingAnythingScala")
- assert(schema.at("/properties/optionalList/title").asText() == "Optional List")
- }
-
- test("java using optional with HTML5") {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorHTML5, testData.pojoUsingOptionalJava)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorHTML5, testData.pojoUsingOptionalJava.getClass, Some(jsonNode))
-
- assertNullableType(schema, "/properties/_string", "string")
- assert(schema.at("/properties/_string/title").asText() == "_string")
-
- assertNullableType(schema, "/properties/_integer", "integer")
- assert(schema.at("/properties/_integer/title").asText() == "_integer")
-
- assert(schema.at("/properties/child1/oneOf/0/type").asText() == "null")
- assert(schema.at("/properties/child1/oneOf/0/title").asText() == "Not included")
- val child1 = getNodeViaRefs(schema, schema.at("/properties/child1/oneOf/1"), "Child1")
- assert(schema.at("/properties/child1/title").asText() == "Child 1")
-
- assertJsonSubTypesInfo(child1, "type", "child1", html5Checks = true)
- assert(child1.at("/properties/parentString/type").asText() == "string")
- assert(child1.at("/properties/child1String/type").asText() == "string")
- assert(child1.at("/properties/_child1String2/type").asText() == "string")
- assert(child1.at("/properties/_child1String3/type").asText() == "string")
-
- assertNullableType(schema, "/properties/optionalList", "array")
- assert(schema.at("/properties/optionalList/oneOf/1/items/$ref").asText() == "#/definitions/ClassNotExtendingAnything")
- assert(schema.at("/properties/optionalList/title").asText() == "Optional List")
- }
-
- test("java using optional with HTML5+nullable") {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorHTML5Nullable, testData.pojoUsingOptionalJava)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorHTML5Nullable, testData.pojoUsingOptionalJava.getClass, Some(jsonNode))
-
- assertNullableType(schema, "/properties/_string", "string")
- assertNullableType(schema, "/properties/_integer", "integer")
-
- assert(schema.at("/properties/child1/oneOf/0/type").asText() == "null")
- assert(schema.at("/properties/child1/oneOf/0/title").asText() == "Not included")
- val child1 = getNodeViaRefs(schema, schema.at("/properties/child1/oneOf/1"), "Child1")
-
- assertJsonSubTypesInfo(child1, "type", "child1", html5Checks = true)
- assertNullableType(child1, "/properties/parentString", "string")
- assertNullableType(child1, "/properties/child1String", "string")
- assertNullableType(child1, "/properties/_child1String2", "string")
-
- // This is required as we have a @JsonProperty marking it as so.
- assert(child1.at("/properties/_child1String3/type").asText() == "string")
- assertPropertyRequired(child1, "_child1String3", required = true)
-
- assertNullableType(schema, "/properties/optionalList", "array")
- assert(schema.at("/properties/optionalList/oneOf/1/items/$ref").asText() == "#/definitions/ClassNotExtendingAnything")
- assert(schema.at("/properties/optionalList/title").asText() == "Optional List")
- }
-
- test("propertyOrdering") {
- {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorHTML5, testData.classNotExtendingAnything)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorHTML5, testData.classNotExtendingAnything.getClass, Some(jsonNode))
-
- assert(schema.at("/properties/someString/propertyOrder").asInt() == 1)
- assert(schema.at("/properties/myEnum/propertyOrder").asInt() == 2)
- }
-
- {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorHTML5Nullable, testData.classNotExtendingAnything)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorHTML5Nullable, testData.classNotExtendingAnything.getClass, Some(jsonNode))
-
- assert(schema.at("/properties/someString/propertyOrder").asInt() == 1)
- assert(schema.at("/properties/myEnum/propertyOrder").asInt() == 2)
- }
-
- // Make sure propertyOrder is not enabled when not using html5
- {
- val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.classNotExtendingAnything)
- val schema = generateAndValidateSchema(jsonSchemaGenerator, testData.classNotExtendingAnything.getClass, Some(jsonNode))
-
- assert(schema.at("/properties/someString/propertyOrder").isMissingNode)
- }
-
- // Same with the non-html5 nullable
- {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorNullable, testData.classNotExtendingAnything)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorNullable, testData.classNotExtendingAnything.getClass, Some(jsonNode))
-
- assert(schema.at("/properties/someString/propertyOrder").isMissingNode)
- }
- }
-
- test("dates") {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorScalaHTML5, testData.manyDates)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorScalaHTML5, testData.manyDates.getClass, Some(jsonNode))
-
- assert(schema.at("/properties/javaLocalDateTime/format").asText() == "datetime-local")
- assert(schema.at("/properties/javaOffsetDateTime/format").asText() == "datetime")
- assert(schema.at("/properties/javaLocalDate/format").asText() == "date")
- assert(schema.at("/properties/jodaLocalDate/format").asText() == "date")
-
- }
-
- test("default and examples") {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorScalaHTML5, testData.defaultAndExamples)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorScalaHTML5, testData.defaultAndExamples.getClass, Some(jsonNode))
-
- assert(getArrayNodeAsListOfStrings(schema.at("/properties/emailValue/examples")) == List("user@example.com"))
- assert(schema.at("/properties/fontSize/default").asText() == "12")
- assert(getArrayNodeAsListOfStrings(schema.at("/properties/fontSize/examples")) == List("10", "14", "18"))
-
- assert(schema.at("/properties/defaultStringViaJsonValue/default").asText() == "ds")
- assert(schema.at("/properties/defaultIntViaJsonValue/default").asText() == "1")
- assert(schema.at("/properties/defaultBoolViaJsonValue/default").asText() == "true")
- }
-
- test("validation") {
- // Scala
- {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorScalaHTML5, testData.classUsingValidation)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorScalaHTML5, testData.classUsingValidation.getClass, Some(jsonNode))
-
- verifyStringProperty(schema, "stringUsingNotNull", Some(1), None, None, required = true)
- verifyStringProperty(schema, "stringUsingNotBlank", Some(1), None, Some("^.*\\S+.*$"), required = true)
- verifyStringProperty(schema, "stringUsingNotBlankAndNotNull", Some(1), None, Some("^.*\\S+.*$"), required = true)
- verifyStringProperty(schema, "stringUsingNotEmpty", Some(1), None, None, required = true)
- verifyStringProperty(schema, "stringUsingSize", Some(1), Some(20), None, required = false)
- verifyStringProperty(schema, "stringUsingSizeOnlyMin", Some(1), None, None, required = false)
- verifyStringProperty(schema, "stringUsingSizeOnlyMax", None, Some(30), None, required = false)
- verifyStringProperty(schema, "stringUsingPattern", None, None, Some("_stringUsingPatternA|_stringUsingPatternB"), required = false)
- verifyStringProperty(schema, "stringUsingPatternList", None, None, Some("^(?=^_stringUsing.*)(?=.*PatternList$).*$"), required = false)
-
- verifyNumericProperty(schema, "intMin", Some(1), None, required = true)
- verifyNumericProperty(schema, "intMax", None, Some(10), required = true)
- verifyNumericProperty(schema, "doubleMin", Some(1), None, required = true)
- verifyNumericProperty(schema, "doubleMax", None, Some(10), required = true)
- verifyNumericDoubleProperty(schema, "decimalMin", Some(1.5), None, required = true)
- verifyNumericDoubleProperty(schema, "decimalMax", None, Some(2.5), required = true)
- assert(schema.at("/properties/email/format").asText() == "email")
-
- verifyArrayProperty(schema, "notEmptyStringArray", Some(1), None, required = true)
-
- verifyObjectProperty(schema, "notEmptyMap", "string", Some(1), None, required = true)
- }
-
- // Java
- {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorScalaHTML5, testData.pojoUsingValidation)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorScalaHTML5, testData.pojoUsingValidation.getClass, Some(jsonNode))
-
- verifyStringProperty(schema, "stringUsingNotNull", Some(1), None, None, required = true)
- verifyStringProperty(schema, "stringUsingNotBlank", Some(1), None, Some("^.*\\S+.*$"), required = true)
- verifyStringProperty(schema, "stringUsingNotBlankAndNotNull", Some(1), None, Some("^.*\\S+.*$"), required = true)
- verifyStringProperty(schema, "stringUsingNotEmpty", Some(1), None, None, required = true)
- verifyStringProperty(schema, "stringUsingSize", Some(1), Some(20), None, required = false)
- verifyStringProperty(schema, "stringUsingSizeOnlyMin", Some(1), None, None, required = false)
- verifyStringProperty(schema, "stringUsingSizeOnlyMax", None, Some(30), None, required = false)
- verifyStringProperty(schema, "stringUsingPattern", None, None, Some("_stringUsingPatternA|_stringUsingPatternB"), required = false)
- verifyStringProperty(schema, "stringUsingPatternList", None, None, Some("^(?=^_stringUsing.*)(?=.*PatternList$).*$"), required = false)
-
- verifyNumericProperty(schema, "intMin", Some(1), None, required = true)
- verifyNumericProperty(schema, "intMax", None, Some(10), required = true)
- verifyNumericProperty(schema, "doubleMin", Some(1), None, required = true)
- verifyNumericProperty(schema, "doubleMax", None, Some(10), required = true)
- verifyNumericDoubleProperty(schema, "decimalMin", Some(1.5), None, required = true)
- verifyNumericDoubleProperty(schema, "decimalMax", None, Some(2.5), required = true)
-
- verifyArrayProperty(schema, "notEmptyStringArray", Some(1), None, required = true)
- verifyArrayProperty(schema, "notEmptyStringList", Some(1), None, required = true)
-
- verifyObjectProperty(schema, "notEmptyStringMap", "string", Some(1), None, required = true)
- }
-
- def verifyStringProperty(schema:JsonNode, propertyName:String, minLength:Option[Int], maxLength:Option[Int], pattern:Option[String], required:Boolean): Unit = {
- assertNumericPropertyValidation(schema, propertyName, "minLength", minLength)
- assertNumericPropertyValidation(schema, propertyName, "maxLength", maxLength)
-
- val matchNode = schema.at(s"/properties/$propertyName/pattern")
- pattern match {
- case Some(_) => assert(matchNode.asText == pattern.get)
- case None => assert(matchNode.isMissingNode)
- }
-
- assertPropertyRequired(schema, propertyName, required)
- }
-
- def verifyNumericProperty(schema:JsonNode, propertyName:String, minimum:Option[Int], maximum:Option[Int], required:Boolean): Unit = {
- assertNumericPropertyValidation(schema, propertyName, "minimum", minimum)
- assertNumericPropertyValidation(schema, propertyName, "maximum", maximum)
- assertPropertyRequired(schema, propertyName, required)
- }
-
- def verifyNumericDoubleProperty(schema:JsonNode, propertyName:String, minimum:Option[Double], maximum:Option[Double], required:Boolean): Unit = {
- assertNumericDoublePropertyValidation(schema, propertyName, "minimum", minimum)
- assertNumericDoublePropertyValidation(schema, propertyName, "maximum", maximum)
- assertPropertyRequired(schema, propertyName, required)
- }
-
-
- def verifyArrayProperty(schema:JsonNode, propertyName:String, minItems:Option[Int], maxItems:Option[Int], required:Boolean): Unit = {
- assertNumericPropertyValidation(schema, propertyName, "minItems", minItems)
- assertNumericPropertyValidation(schema, propertyName, "maxItems", maxItems)
- assertPropertyRequired(schema, propertyName, required)
- }
-
- def verifyObjectProperty(schema:JsonNode, propertyName:String, additionalPropertiesType:String, minProperties:Option[Int], maxProperties:Option[Int], required:Boolean): Unit = {
- assert(schema.at(s"/properties/$propertyName/additionalProperties/type").asText() == additionalPropertiesType)
- assertNumericPropertyValidation(schema, propertyName, "minProperties", minProperties)
- assertNumericPropertyValidation(schema, propertyName, "maxProperties", maxProperties)
- assertPropertyRequired(schema, propertyName, required)
- }
-
- def assertNumericPropertyValidation(schema:JsonNode, propertyName:String, validationName:String, value:Option[Int]): Unit = {
- val jsonNode = schema.at(s"/properties/$propertyName/$validationName")
- value match {
- case Some(_) => assert(jsonNode.asInt == value.get)
- case None => assert(jsonNode.isMissingNode)
- }
- }
-
- def assertNumericDoublePropertyValidation(schema:JsonNode, propertyName:String, validationName:String, value:Option[Double]): Unit = {
- val jsonNode = schema.at(s"/properties/$propertyName/$validationName")
- value match {
- case Some(_) => assert(jsonNode.asDouble() == value.get)
- case None => assert(jsonNode.isMissingNode)
- }
- }
- }
-
- test("validation using groups") {
-
- def check(schema:JsonNode, propertyName:String, included:Boolean): Unit = {
- assertPropertyRequired(schema, propertyName, required = included)
- assert(schema.at(s"/properties/$propertyName/injected").isMissingNode != included)
- }
-
- val objectUsingGroups = testData.classUsingValidationWithGroups
-
- // no Group at all
- {
- val jsonSchemaGenerator_Group = new JsonSchemaGenerator(_objectMapperScala, debug = true,
- JsonSchemaConfig.vanillaJsonSchemaDraft4.copy(
- javaxValidationGroups = Array()
- ))
-
- val jsonNode = assertToFromJson(jsonSchemaGenerator_Group, objectUsingGroups)
- val schema = generateAndValidateSchema(jsonSchemaGenerator_Group, objectUsingGroups.getClass, Some(jsonNode))
-
- check(schema, "noGroup", included = true)
- check(schema, "defaultGroup", included = true)
- check(schema, "group1", included = false)
- check(schema, "group2", included = false)
- check(schema, "group12", included = false)
-
- // Make sure inject on class-level is not included
- assert(schema.at(s"/injected").isMissingNode)
- }
-
- // Default group
- {
- val jsonSchemaGenerator_Group = new JsonSchemaGenerator(_objectMapperScala, debug = true,
- JsonSchemaConfig.vanillaJsonSchemaDraft4.copy(
- javaxValidationGroups = Array(classOf[Default])
- ))
-
- val jsonNode = assertToFromJson(jsonSchemaGenerator_Group, objectUsingGroups)
- val schema = generateAndValidateSchema(jsonSchemaGenerator_Group, objectUsingGroups.getClass, Some(jsonNode))
-
- check(schema, "noGroup", included = true)
- check(schema, "defaultGroup", included = true)
- check(schema, "group1", included = false)
- check(schema, "group2", included = false)
- check(schema, "group12", included = false)
-
- // Make sure inject on class-level is not included
- assert(schema.at(s"/injected").isMissingNode)
- }
-
- // Group 1
- {
- val jsonSchemaGenerator_Group = new JsonSchemaGenerator(_objectMapperScala, debug = true,
- JsonSchemaConfig.vanillaJsonSchemaDraft4.copy(
- javaxValidationGroups = Array(classOf[ValidationGroup1])
- ))
-
- val jsonNode = assertToFromJson(jsonSchemaGenerator_Group, objectUsingGroups)
- val schema = generateAndValidateSchema(jsonSchemaGenerator_Group, objectUsingGroups.getClass, Some(jsonNode))
-
- check(schema, "noGroup", included = false)
- check(schema, "defaultGroup", included = false)
- check(schema, "group1", included = true)
- check(schema, "group2", included = false)
- check(schema, "group12", included = true)
-
- // Make sure inject on class-level is not included
- assert(!schema.at(s"/injected").isMissingNode)
- }
-
- // Group 1 and Default-group
- {
- val jsonSchemaGenerator_Group = new JsonSchemaGenerator(_objectMapperScala, debug = true,
- JsonSchemaConfig.vanillaJsonSchemaDraft4.copy(
- javaxValidationGroups = Array(classOf[ValidationGroup1], classOf[Default])
- ))
-
- val jsonNode = assertToFromJson(jsonSchemaGenerator_Group, objectUsingGroups)
- val schema = generateAndValidateSchema(jsonSchemaGenerator_Group, objectUsingGroups.getClass, Some(jsonNode))
-
- check(schema, "noGroup", included = true)
- check(schema, "defaultGroup", included = true)
- check(schema, "group1", included = true)
- check(schema, "group2", included = false)
- check(schema, "group12", included = true)
-
- // Make sure inject on class-level is not included
- assert(!schema.at(s"/injected").isMissingNode)
- }
-
- // Group 2
- {
- val jsonSchemaGenerator_Group = new JsonSchemaGenerator(_objectMapperScala, debug = true,
- JsonSchemaConfig.vanillaJsonSchemaDraft4.copy(
- javaxValidationGroups = Array(classOf[ValidationGroup2])
- ))
-
- val jsonNode = assertToFromJson(jsonSchemaGenerator_Group, objectUsingGroups)
- val schema = generateAndValidateSchema(jsonSchemaGenerator_Group, objectUsingGroups.getClass, Some(jsonNode))
-
- check(schema, "noGroup", included = false)
- check(schema, "defaultGroup", included = false)
- check(schema, "group1", included = false)
- check(schema, "group2", included = true)
- check(schema, "group12", included = true)
-
- // Make sure inject on class-level is not included
- assert(schema.at(s"/injected").isMissingNode)
- }
-
- // Group 1 and 2
- {
- val jsonSchemaGenerator_Group = new JsonSchemaGenerator(_objectMapperScala, debug = true,
- JsonSchemaConfig.vanillaJsonSchemaDraft4.copy(
- javaxValidationGroups = Array(classOf[ValidationGroup1], classOf[ValidationGroup2])
- ))
-
- val jsonNode = assertToFromJson(jsonSchemaGenerator_Group, objectUsingGroups)
- val schema = generateAndValidateSchema(jsonSchemaGenerator_Group, objectUsingGroups.getClass, Some(jsonNode))
-
- check(schema, "noGroup", included = false)
- check(schema, "defaultGroup", included = false)
- check(schema, "group1", included = true)
- check(schema, "group2", included = true)
- check(schema, "group12", included = true)
-
- // Make sure inject on class-level is not included
- assert(!schema.at(s"/injected").isMissingNode)
- }
-
- // Group 3 - not in use
- {
- val jsonSchemaGenerator_Group = new JsonSchemaGenerator(_objectMapperScala, debug = true,
- JsonSchemaConfig.vanillaJsonSchemaDraft4.copy(
- javaxValidationGroups = Array(classOf[ValidationGroup3_notInUse])
- ))
-
- val jsonNode = assertToFromJson(jsonSchemaGenerator_Group, objectUsingGroups)
- val schema = generateAndValidateSchema(jsonSchemaGenerator_Group, objectUsingGroups.getClass, Some(jsonNode))
-
- check(schema, "noGroup", included = false)
- check(schema, "defaultGroup", included = false)
- check(schema, "group1", included = false)
- check(schema, "group2", included = false)
- check(schema, "group12", included = false)
-
- // Make sure inject on class-level is not included
- assert(schema.at(s"/injected").isMissingNode)
- }
-
- }
-
- test("Polymorphism using mixin") {
- // Java
- {
- val jsonNode = assertToFromJson(jsonSchemaGenerator, testData.mixinChild1)
- assertToFromJson(jsonSchemaGenerator, testData.mixinChild1, classOf[MixinParent])
-
- val schema = generateAndValidateSchema(jsonSchemaGenerator, classOf[MixinParent], Some(jsonNode))
-
- assertChild1(schema, "/oneOf", defName = "MixinChild1")
- assertChild2(schema, "/oneOf", defName = "MixinChild2")
- }
-
- // Java + Nullable types
- {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorNullable, testData.mixinChild1)
- assertToFromJson(jsonSchemaGeneratorNullable, testData.mixinChild1, classOf[MixinParent])
-
- val schema = generateAndValidateSchema(jsonSchemaGeneratorNullable, classOf[MixinParent], Some(jsonNode))
-
- assertNullableChild1(schema, "/oneOf", defName = "MixinChild1")
- assertNullableChild2(schema, "/oneOf", defName = "MixinChild2")
- }
- }
-
- test("issue 24") {
- jsonSchemaGenerator.generateJsonSchema(classOf[EntityWrapper])
- jsonSchemaGeneratorNullable.generateJsonSchema(classOf[EntityWrapper])
- }
-
- test("Polymorphism oneOf-ordering") {
- val schema = generateAndValidateSchema(jsonSchemaGeneratorScalaHTML5, classOf[PolymorphismOrderingParentScala], None)
- val oneOfList:List[String] = schema.at("/oneOf").asInstanceOf[ArrayNode].iterator().asScala.toList.map(_.at("/$ref").asText)
- assert(List("#/definitions/PolymorphismOrderingChild3", "#/definitions/PolymorphismOrderingChild1", "#/definitions/PolymorphismOrderingChild4", "#/definitions/PolymorphismOrderingChild2") == oneOfList)
- }
-
- test("@NotNull annotations and nullable types") {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorNullable, testData.notNullableButNullBoolean)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorNullable, testData.notNullableButNullBoolean.getClass, None)
-
- val exception = intercept[Exception] {
- useSchema(schema, Some(jsonNode))
- }
-
- // While our compiler will let us do what we're about to do, the validator should give us a message that looks like this...
- assert(exception.getMessage.contains("json does not validate against schema"))
- assert(exception.getMessage.contains("error: instance type (null) does not match any allowed primitive type (allowed: [\"boolean\"])"))
-
- assert(schema.at("/properties/notNullBooleanObject/type").asText() == "boolean")
- assertPropertyRequired(schema, "notNullBooleanObject", required = true)
- }
-
- test("nestedPolymorphism") {
- val jsonNode = assertToFromJson(jsonSchemaGeneratorScala, testData.nestedPolymorphism)
- assertToFromJson(jsonSchemaGeneratorScala, testData.nestedPolymorphism, classOf[NestedPolymorphism1Base])
-
- generateAndValidateSchema(jsonSchemaGeneratorScala, classOf[NestedPolymorphism1Base], Some(jsonNode))
- }
-
- test("PolymorphismAndTitle") {
- val schema = jsonSchemaGeneratorScala.generateJsonSchema(classOf[PolymorphismAndTitleBase])
-
- println("--------------------------------------------")
- println(asPrettyJson(schema, jsonSchemaGeneratorScala.rootObjectMapper))
-
- assert( schema.at("/oneOf/0/$ref").asText() == "#/definitions/PolymorphismAndTitle1")
- assert( schema.at("/oneOf/0/title").asText() == "CustomTitle1")
- }
-
- test("UsingJsonSchemaOptions") {
-
- {
- val schema = jsonSchemaGeneratorScala.generateJsonSchema(classOf[UsingJsonSchemaOptions])
-
- println("--------------------------------------------")
- println(asPrettyJson(schema, jsonSchemaGeneratorScala.rootObjectMapper))
-
- assert(schema.at("/options/classOption").asText() == "classOptionValue")
- assert(schema.at("/properties/propertyUsingOneProperty/options/o1").asText() == "v1")
- }
-
- {
- val schema = jsonSchemaGeneratorScala.generateJsonSchema(classOf[UsingJsonSchemaOptionsBase])
-
- println("--------------------------------------------")
- println(asPrettyJson(schema, jsonSchemaGeneratorScala.rootObjectMapper))
-
- assert(schema.at("/definitions/UsingJsonSchemaOptionsChild1/options/classOption1").asText() == "classOptionValue1")
- assert(schema.at("/definitions/UsingJsonSchemaOptionsChild1/properties/propertyUsingOneProperty/options/o1").asText() == "v1")
-
- assert(schema.at("/definitions/UsingJsonSchemaOptionsChild2/options/classOption2").asText() == "classOptionValue2")
- assert(schema.at("/definitions/UsingJsonSchemaOptionsChild2/properties/propertyUsingOneProperty/options/o1").asText() == "v1")
- }
- }
-
- test("UsingJsonSchemaInject") {
- {
-
- val customUserNameLoaderVariable = "xx"
- val customUserNamesLoader = new CustomUserNamesLoader(customUserNameLoaderVariable)
-
- val config = JsonSchemaConfig.vanillaJsonSchemaDraft4.copy(jsonSuppliers = Map("myCustomUserNamesLoader" -> customUserNamesLoader))
- val _jsonSchemaGeneratorScala = new JsonSchemaGenerator(_objectMapperScala, debug = true, config)
- val schema = _jsonSchemaGeneratorScala.generateJsonSchema(classOf[UsingJsonSchemaInject])
-
- println("--------------------------------------------")
- println(asPrettyJson(schema, _jsonSchemaGeneratorScala.rootObjectMapper))
-
- assert(schema.at("/patternProperties/^s[a-zA-Z0-9]+/type").asText() == "string")
- assert(schema.at("/patternProperties/^i[a-zA-Z0-9]+/type").asText() == "integer")
- assert(schema.at("/properties/sa/type").asText() == "string")
- assert(schema.at("/properties/injectedInProperties").asText() == "true")
- assert(schema.at("/properties/sa/options/hidden").asText() == "true")
- assert(schema.at("/properties/saMergeFalse/type").asText() == "integer")
- assert(schema.at("/properties/saMergeFalse/default").asText() == "12")
- assert(schema.at("/properties/saMergeFalse/pattern").isMissingNode)
- assert(schema.at("/properties/ib/type").asText() == "integer")
- assert(schema.at("/properties/ib/multipleOf").asInt() == 7)
- assert(schema.at("/properties/ib/exclusiveMinimum").asBoolean())
- assert(schema.at("/properties/uns/items/enum/0").asText() == "foo")
- assert(schema.at("/properties/uns/items/enum/1").asText() == "bar")
- assert(schema.at("/properties/uns2/items/enum/0").asText() == "foo_" + customUserNameLoaderVariable)
- assert(schema.at("/properties/uns2/items/enum/1").asText() == "bar_" + customUserNameLoaderVariable)
- }
- }
-
- test("UsingJsonSchemaInjectWithTopLevelMergeFalse") {
-
- val config = JsonSchemaConfig.vanillaJsonSchemaDraft4
- val _jsonSchemaGeneratorScala = new JsonSchemaGenerator(_objectMapperScala, debug = true, config)
- val schema = _jsonSchemaGeneratorScala.generateJsonSchema(classOf[UsingJsonSchemaInjectWithTopLevelMergeFalse])
-
- val schemaJson = asPrettyJson(schema, _jsonSchemaGeneratorScala.rootObjectMapper)
- println("--------------------------------------------")
- println(schemaJson)
-
- val fasit =
- """{
- | "everything" : "should be replaced"
- |}""".stripMargin
-
- assert( schemaJson == fasit )
- }
-
- test("Preventing polymorphism by using classTypeReMapping") {
-
- val config = JsonSchemaConfig.vanillaJsonSchemaDraft4.copy(classTypeReMapping = Map(classOf[Parent] -> classOf[Child1]))
- val _jsonSchemaGenerator = new JsonSchemaGenerator(_objectMapper, debug = true, config)
-
-
- // Class with property
- {
- def assertDefaultValues(schema: JsonNode): Unit = {
- assert(schema.at("/properties/stringWithDefault/type").asText() == "string")
- assert(schema.at("/properties/stringWithDefault/default").asText() == "x")
- assert(schema.at("/properties/intWithDefault/type").asText() == "integer")
- assert(schema.at("/properties/intWithDefault/default").asInt() == 12)
- assert(schema.at("/properties/booleanWithDefault/type").asText() == "boolean")
- assert(schema.at("/properties/booleanWithDefault/default").asBoolean())
- }
-
- // PojoWithParent has a property of type Parent (which uses polymorphism).
- // Default rendering schema will make this property oneOf Child1 and Child2.
- // In this test we're preventing this by remapping Parent to Child1.
- // Now, when generating the schema, we should generate it as if the property where of type Child1
-
- val jsonNode = assertToFromJson(_jsonSchemaGenerator, testData.pojoWithParent)
- assertToFromJson(_jsonSchemaGenerator, testData.pojoWithParent, classOf[PojoWithParent])
-
- val schema = generateAndValidateSchema(_jsonSchemaGenerator, classOf[PojoWithParent], Some(jsonNode))
-
- assert(!schema.at("/additionalProperties").asBoolean())
- assert(schema.at("/properties/pojoValue/type").asText() == "boolean")
- assertDefaultValues(schema)
-
- assertChild1(schema, "/properties/child")
- }
-
- // remapping root class
- {
- def doTest(pojo:Object, clazz:Class[_], g:JsonSchemaGenerator): Unit = {
- val jsonNode = assertToFromJson(g, pojo)
- val schema = generateAndValidateSchema(g, clazz, Some(jsonNode))
-
- assert(!schema.at("/additionalProperties").asBoolean())
- assert(schema.at("/properties/parentString/type").asText() == "string")
- assertJsonSubTypesInfo(schema, "type", "child1")
- }
-
- doTest(testData.child1, classOf[Parent], _jsonSchemaGenerator)
-
- }
-
- //remapping arrays
- {
- def doTest(pojo:Object, clazz:Class[_], g:JsonSchemaGenerator, html5Checks:Boolean): Unit ={
-
- val jsonNode = assertToFromJson(g, pojo)
- val schema = generateAndValidateSchema(g, clazz, Some(jsonNode))
-
- assert(schema.at("/properties/intArray1/type").asText() == "array")
- assert(schema.at("/properties/intArray1/items/type").asText() == "integer")
-
- assert(schema.at("/properties/stringArray/type").asText() == "array")
- assert(schema.at("/properties/stringArray/items/type").asText() == "string")
-
- assert(schema.at("/properties/stringList/type").asText() == "array")
- assert(schema.at("/properties/stringList/items/type").asText() == "string")
- assert(schema.at("/properties/stringList/minItems").asInt() == 1)
- assert(schema.at("/properties/stringList/maxItems").asInt() == 10)
-
- assert(schema.at("/properties/polymorphismList/type").asText() == "array")
- assertChild1(schema, "/properties/polymorphismList/items", html5Checks = html5Checks)
-
-
- assert(schema.at("/properties/polymorphismArray/type").asText() == "array")
- assertChild1(schema, "/properties/polymorphismArray/items", html5Checks = html5Checks)
-
- assert(schema.at("/properties/listOfListOfStrings/type").asText() == "array")
- assert(schema.at("/properties/listOfListOfStrings/items/type").asText() == "array")
- assert(schema.at("/properties/listOfListOfStrings/items/items/type").asText() == "string")
-
- assert(schema.at("/properties/setOfUniqueValues/type").asText() == "array")
- assert(schema.at("/properties/setOfUniqueValues/items/type").asText() == "string")
-
- if (html5Checks) {
- assert(schema.at("/properties/setOfUniqueValues/uniqueItems").asText() == "true")
- assert(schema.at("/properties/setOfUniqueValues/format").asText() == "checkbox")
- }
- }
-
- val c = new Child1()
- c.parentString = "pv"
- c.child1String = "cs"
- c.child1String2 = "cs2"
- c.child1String3 = "cs3"
-
- val _classNotExtendingAnything = {
- val o = new ClassNotExtendingAnything
- o.someString = "Something"
- o.myEnum = MyEnum.C
- o
- }
-
- val _pojoWithArrays = new PojoWithArrays(
- Array(1,2,3),
- Array("a1","a2","a3"),
- List("l1", "l2", "l3").asJava,
- List[Parent](c, c).asJava,
- List[Parent](c, c).toArray,
- List(_classNotExtendingAnything, _classNotExtendingAnything).asJava,
- PojoWithArrays._listOfListOfStringsValues, // It was difficult to construct this from scala :)
- Set(MyEnum.B).asJava
- )
-
- doTest(_pojoWithArrays, _pojoWithArrays.getClass, _jsonSchemaGenerator, html5Checks = false)
-
- }
-
- }
-
- test("Basic json (de)serialization of Kotlin data class") {
- val a = new KotlinClass("a", 1)
- val json = _objectMapperKotlin.writeValueAsString(a)
- val r = _objectMapperKotlin.readValue(json, classOf[KotlinClass])
- assert( a == r)
- }
-
- test("Non-nullable parameter with default value is always required for Kotlin class") {
-
- val jsonNode = assertToFromJson(jsonSchemaGeneratorKotlin, testData.kotlinWithDefaultValues)
- val schema = generateAndValidateSchema(jsonSchemaGeneratorKotlin, testData.kotlinWithDefaultValues.getClass, Some(jsonNode))
-
- println(schema)
- assert("string" == schema.at("/properties/optional/type").asText())
- assert("string" == schema.at("/properties/required/type").asText())
- assert("string" == schema.at("/properties/optionalDefault/type").asText())
- assert("string" == schema.at("/properties/optionalDefaultNull/type").asText())
-
- assertPropertyRequired(schema, "optional", required = false)
- assertPropertyRequired(schema, "required", required = true)
- assertPropertyRequired(schema, "optionalDefault", required = true)
- assertPropertyRequired(schema, "optionalDefaultNull", required = false)
-
- }
-
- test("JsonSchema DRAFT-06") {
- val jsg = jsonSchemaGenerator_draft_06
- val jsonNode = assertToFromJson(jsg, testData.classNotExtendingAnything)
- val schema = generateAndValidateSchema(jsg, testData.classNotExtendingAnything.getClass, Some(jsonNode),
- jsonSchemaDraft = JsonSchemaDraft.DRAFT_06
- )
-
- // Currently there are no differences in the generated jsonSchema other than the $schema-url
- }
-
- test("JsonSchema DRAFT-07") {
- val jsg = jsonSchemaGenerator_draft_07
- val jsonNode = assertToFromJson(jsg, testData.classNotExtendingAnything)
- val schema = generateAndValidateSchema(jsg, testData.classNotExtendingAnything.getClass, Some(jsonNode),
- jsonSchemaDraft = JsonSchemaDraft.DRAFT_07
- )
-
- // Currently there are no differences in the generated jsonSchema other than the $schema-url
- }
-
- test("JsonSchema DRAFT-2019-09") {
- val jsg = jsonSchemaGenerator_draft_2019_09
- val jsonNode = assertToFromJson(jsg, testData.classNotExtendingAnything)
- val schema = generateAndValidateSchema(jsg, testData.classNotExtendingAnything.getClass, Some(jsonNode),
- jsonSchemaDraft = JsonSchemaDraft.DRAFT_2019_09
- )
-
- // Currently there are no differences in the generated jsonSchema other than the $schema-url
- }
-
-}
-
-trait TestData {
- import scala.collection.JavaConverters._
- val child1 = {
- val c = new Child1()
- c.parentString = "pv"
- c.child1String = "cs"
- c.child1String2 = "cs2"
- c.child1String3 = "cs3"
- c
- }
- val child2 = {
- val c = new Child2()
- c.parentString = "pv"
- c.child2int = 12
- c
- }
- val pojoWithParent = {
- val p = new PojoWithParent
- p.pojoValue = true
- p.child = child1
- p.stringWithDefault = "y"
- p.intWithDefault = 13
- p.booleanWithDefault = true
- p
- }
-
- val child21 = {
- val c = new Child21()
- c.parentString = "pv"
- c.child1String = "cs"
- c.child1String2 = "cs2"
- c.child1String3 = "cs3"
- c
- }
- val child22 = {
- val c = new Child22()
- c.parentString = "pv"
- c.child2int = 12
- c
- }
-
- val child31 = {
- val c = new Child31()
- c.parentString = "pv"
- c.child1String = "cs"
- c.child1String2 = "cs2"
- c.child1String3 = "cs3"
- c
- }
- val child32 = {
- val c = new Child32()
- c.parentString = "pv"
- c.child2int = 12
- c
- }
-
- val child41 = new Child41()
- val child42 = new Child42()
-
- val child51 = {
- val c = new Child51()
- c.parentString = "pv"
- c.child1String = "cs"
- c.child1String2 = "cs2"
- c.child1String3 = "cs3"
- c
- }
- val child52 = {
- val c = new Child52()
- c.parentString = "pv"
- c.child2int = 12
- c
- }
- val child61 = {
- val c = new Child61()
- c.parentString = "pv"
- c.child1String = "cs"
- c.child1String2 = "cs2"
- c.child1String3 = "cs3"
- c
- }
-
- val child2Scala = Child2Scala("pv", 12)
- val child1Scala = Child1Scala("pv", "cs", "cs2", "cs3")
- val pojoWithParentScala = PojoWithParentScala(pojoValue = true, child1Scala, "y", 13, booleanWithDefault = true)
-
- val classNotExtendingAnything = {
- val o = new ClassNotExtendingAnything
- o.someString = "Something"
- o.myEnum = MyEnum.C
- o
- }
-
- val classNotExtendingAnythingScala = ClassNotExtendingAnythingScala("Something", MyEnum.C, Some(MyEnum.A))
-
- val manyPrimitives = new ManyPrimitives("s1", 1, 2, true, false, true, 0.1, 0.2, MyEnum.B)
-
- val manyPrimitivesNulls = new ManyPrimitives(null, null, 1, null, false, false, null, 0.1, null)
-
- val manyPrimitivesScala = ManyPrimitivesScala("s1", 1, _boolean = true, 0.1)
-
- val pojoUsingOptionScala = PojoUsingOptionScala(Some("s1"), Some(1), Some(true), Some(0.1), Some(child1Scala), Some(List(classNotExtendingAnythingScala)))
-
- val pojoUsingOptionalJava = new PojoUsingOptionalJava(Optional.of("s"), Optional.of(1), Optional.of(child1), Optional.of(util.Arrays.asList(classNotExtendingAnything)))
-
- val pojoWithCustomSerializer = {
- val p = new PojoWithCustomSerializer
- p.myString = "xxx"
- p
- }
-
- val objectWithPropertyWithCustomSerializer = new ObjectWithPropertyWithCustomSerializer("s1", pojoWithCustomSerializer)
-
- val pojoWithArrays = new PojoWithArrays(
- Array(1,2,3),
- Array("a1","a2","a3"),
- List("l1", "l2", "l3").asJava,
- List(child1, child2).asJava,
- List(child1, child2).toArray,
- List(classNotExtendingAnything, classNotExtendingAnything).asJava,
- PojoWithArrays._listOfListOfStringsValues, // It was difficult to construct this from scala :)
- Set(MyEnum.B).asJava
- )
-
- val pojoWithArraysNullable = new PojoWithArraysNullable(
- Array(1,2,3),
- Array("a1","a2","a3"),
- List("l1", "l2", "l3").asJava,
- List(child1, child2).asJava,
- List(child1, child2).toArray,
- List(classNotExtendingAnything, classNotExtendingAnything).asJava,
- PojoWithArrays._listOfListOfStringsValues, // It was difficult to construct this from scala :)
- Set(MyEnum.B).asJava
- )
-
- val pojoWithArraysScala = PojoWithArraysScala(
- Some(List(1,2,3)),
- List("a1","a2","a3"),
- List("l1", "l2", "l3"),
- List(child1, child2),
- List(child1, child2),
- List(classNotExtendingAnything, classNotExtendingAnything),
- List(List("l11","l12"), List("l21")),
- setOfUniqueValues = Set(MyEnum.B)
- )
-
- val recursivePojo = new RecursivePojo("t1", List(new RecursivePojo("c1", null)).asJava)
-
- val pojoUsingMaps = new PojoUsingMaps(
- Map[String, Integer]("a" -> 1, "b" -> 2).asJava,
- Map("x" -> "y", "z" -> "w").asJava,
- Map[String, Parent]("1" -> child1, "2" -> child2).asJava
- )
-
- val pojoUsingFormat = new PojoUsingFormat("test@example.com", true, OffsetDateTime.now(), OffsetDateTime.now())
- val manyDates = ManyDates(LocalDateTime.now(), OffsetDateTime.now(), LocalDate.now(), org.joda.time.LocalDate.now())
-
- val defaultAndExamples = DefaultAndExamples("email@example.com", 18, "s", 2, false)
-
- val classUsingValidation = ClassUsingValidation(
- "_stringUsingNotNull", "_stringUsingNotBlank", "_stringUsingNotBlankAndNotNull", "_stringUsingNotEmpty", List("l1", "l2", "l3"), Map("mk1" -> "mv1", "mk2" -> "mv2"),
- "_stringUsingSize", "_stringUsingSizeOnlyMin", "_stringUsingSizeOnlyMax", "_stringUsingPatternA", "_stringUsingPatternList",
- 1, 2, 1.0, 2.0, 1.6, 2.0, "mbk@kjetland.com"
- )
-
- val classUsingValidationWithGroups = ClassUsingValidationWithGroups(
- "_noGroup", "_defaultGroup", "_group1", "_group2", "_group12"
- )
-
- val pojoUsingValidation = new PojoUsingValidation(
- "_stringUsingNotNull", "_stringUsingNotBlank", "_stringUsingNotBlankAndNotNull", "_stringUsingNotEmpty", Array("a1", "a2", "a3"), List("l1", "l2", "l3").asJava,
- Map("mk1" -> "mv1", "mk2" -> "mv2").asJava, "_stringUsingSize", "_stringUsingSizeOnlyMin", "_stringUsingSizeOnlyMax", "_stringUsingPatternA",
- "_stringUsingPatternList", 1, 2, 1.0, 2.0, 1.6, 2.0
- )
-
- val mixinChild1 = {
- val c = new MixinChild1()
- c.parentString = "pv"
- c.child1String = "cs"
- c.child1String2 = "cs2"
- c.child1String3 = "cs3"
- c
- }
-
- // Test the collision of @NotNull validations and null fields.
- val notNullableButNullBoolean = new PojoWithNotNull(null)
-
- val nestedPolymorphism = NestedPolymorphism1_1("a1", NestedPolymorphism2_2("a2", Some(NestedPolymorphism3("b3"))))
-
- val genericClassVoid = new GenericClassVoid()
-
- val genericMapLike = new GenericMapLike(Collections.singletonMap("foo", "bar"))
-
- val kotlinWithDefaultValues = new KotlinWithDefaultValues("1", "2", "3", "4")
-
-}
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/Child1Scala.scala b/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/Child1Scala.scala
deleted file mode 100644
index 6491224..0000000
--- a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/Child1Scala.scala
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.kjetland.jackson.jsonSchema.testDataScala
-
-import com.fasterxml.jackson.annotation.JsonProperty
-
-case class Child1Scala
-(
- parentString:String,
- child1String:String,
-
- @JsonProperty("_child1String2")
- child1String2:String,
-
- @JsonProperty(value = "_child1String3", required = true)
- child1String3:String
-) extends ParentScala
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/Child2Scala.scala b/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/Child2Scala.scala
deleted file mode 100644
index 69aceb5..0000000
--- a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/Child2Scala.scala
+++ /dev/null
@@ -1,3 +0,0 @@
-package com.kjetland.jackson.jsonSchema.testDataScala
-
-case class Child2Scala(parentString:String, child2int:Int) extends ParentScala
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/ClassNotExtendingAnythingScala.scala b/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/ClassNotExtendingAnythingScala.scala
deleted file mode 100644
index 7040eb7..0000000
--- a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/ClassNotExtendingAnythingScala.scala
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.kjetland.jackson.jsonSchema.testDataScala
-
-import com.kjetland.jackson.jsonSchema.testData.MyEnum
-
-case class ClassNotExtendingAnythingScala(someString:String, myEnum: MyEnum, myEnumO: Option[MyEnum])
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/ClassUsingValidation.scala b/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/ClassUsingValidation.scala
deleted file mode 100644
index b907191..0000000
--- a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/ClassUsingValidation.scala
+++ /dev/null
@@ -1,90 +0,0 @@
-package com.kjetland.jackson.jsonSchema.testDataScala
-
-import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaInject
-import javax.validation.constraints._
-import javax.validation.groups.Default
-
-case class ClassUsingValidation
-(
- @NotNull
- stringUsingNotNull:String,
-
- @NotBlank
- stringUsingNotBlank:String,
-
- @NotNull
- @NotBlank
- stringUsingNotBlankAndNotNull:String,
-
- @NotEmpty
- stringUsingNotEmpty:String,
-
- @NotEmpty
- notEmptyStringArray:List[String], // Per PojoArraysWithScala, we use always use Lists in Scala, and never raw arrays.
-
- @NotEmpty
- notEmptyMap:Map[String, String],
-
- @Size(min=1, max=20)
- stringUsingSize:String,
-
- @Size(min=1)
- stringUsingSizeOnlyMin:String,
-
- @Size(max=30)
- stringUsingSizeOnlyMax:String,
-
- @Pattern(regexp = "_stringUsingPatternA|_stringUsingPatternB")
- stringUsingPattern:String,
-
- @Pattern.List(Array(
- new Pattern(regexp = "^_stringUsing.*"),
- new Pattern(regexp = ".*PatternList$")
- ))
- stringUsingPatternList:String,
-
- @Min(1)
- intMin:Int,
- @Max(10)
- intMax:Int,
- @Min(1)
- doubleMin:Double,
- @Max(10)
- doubleMax:Double,
- @DecimalMin("1.5")
- decimalMin:Double,
- @DecimalMax("2.5")
- decimalMax:Double,
-
- @Email
- email:String
-)
-
-trait ValidationGroup1
-trait ValidationGroup2
-trait ValidationGroup3_notInUse
-
-@JsonSchemaInject(json = """{"injected":true}""", javaxValidationGroups = Array(classOf[ValidationGroup1]))
-case class ClassUsingValidationWithGroups
-(
- @NotNull
- @JsonSchemaInject(json = """{"injected":true}""")
- noGroup:String,
-
- @NotNull(groups = Array(classOf[Default]))
- @JsonSchemaInject(json = """{"injected":true}""", javaxValidationGroups = Array(classOf[Default]))
- defaultGroup:String,
-
- @NotNull(groups = Array(classOf[ValidationGroup1]))
- @JsonSchemaInject(json = """{"injected":true}""", javaxValidationGroups = Array(classOf[ValidationGroup1]))
- group1:String,
-
- @NotNull(groups = Array(classOf[ValidationGroup2]))
- @JsonSchemaInject(json = """{"injected":true}""", javaxValidationGroups = Array(classOf[ValidationGroup2]))
- group2:String,
-
- @NotNull(groups = Array(classOf[ValidationGroup1], classOf[ValidationGroup2]))
- @JsonSchemaInject(json = """{"injected":true}""", javaxValidationGroups = Array(classOf[ValidationGroup1], classOf[ValidationGroup2]))
- group12:String
-
-)
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/DefaultAndExamples.scala b/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/DefaultAndExamples.scala
deleted file mode 100644
index 1a341d4..0000000
--- a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/DefaultAndExamples.scala
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.kjetland.jackson.jsonSchema.testDataScala
-
-import com.fasterxml.jackson.annotation.JsonProperty
-import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaDefault
-import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaExamples;
-
-case class DefaultAndExamples
-(
- @JsonSchemaExamples(Array("user@example.com"))
- emailValue:String,
- @JsonSchemaDefault("12")
- @JsonSchemaExamples(Array("10", "14", "18"))
- fontSize:Int,
-
- @JsonProperty( defaultValue = "ds")
- defaultStringViaJsonValue:String,
- @JsonProperty( defaultValue = "1")
- defaultIntViaJsonValue:Int,
- @JsonProperty( defaultValue = "true")
- defaultBoolViaJsonValue:Boolean
-)
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/ManyDates.scala b/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/ManyDates.scala
deleted file mode 100644
index 0f46d2f..0000000
--- a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/ManyDates.scala
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.kjetland.jackson.jsonSchema.testDataScala
-
-import java.time.{LocalDate, LocalDateTime, OffsetDateTime}
-
-case class ManyDates
-(
- javaLocalDateTime:LocalDateTime,
- javaOffsetDateTime:OffsetDateTime,
- javaLocalDate:LocalDate,
- jodaLocalDate:org.joda.time.LocalDate
-)
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/ManyPrimitivesScala.scala b/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/ManyPrimitivesScala.scala
deleted file mode 100644
index 21ac031..0000000
--- a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/ManyPrimitivesScala.scala
+++ /dev/null
@@ -1,3 +0,0 @@
-package com.kjetland.jackson.jsonSchema.testDataScala
-
-case class ManyPrimitivesScala(_string:String, _integer:Int, _boolean:Boolean, _double:Double)
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/NestedPolymorphism.scala b/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/NestedPolymorphism.scala
deleted file mode 100644
index d72540a..0000000
--- a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/NestedPolymorphism.scala
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.kjetland.jackson.jsonSchema.testDataScala
-
-import com.fasterxml.jackson.annotation.{JsonSubTypes, JsonTypeInfo}
-
-case class NestedPolymorphism3(b:String)
-
-@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
-@JsonSubTypes(Array(
- new JsonSubTypes.Type(value = classOf[NestedPolymorphism2_1], name = "NestedPolymorphism2_1"),
- new JsonSubTypes.Type(value = classOf[NestedPolymorphism2_2], name = "NestedPolymorphism2_2")
-))
-trait NestedPolymorphism2Base
-
-case class NestedPolymorphism2_1(a:String, pojo:Option[NestedPolymorphism3]) extends NestedPolymorphism2Base
-case class NestedPolymorphism2_2(a:String, pojo:Option[NestedPolymorphism3]) extends NestedPolymorphism2Base
-
-@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
-@JsonSubTypes(Array(
- new JsonSubTypes.Type(value = classOf[NestedPolymorphism1_1], name = "NestedPolymorphism1_1"),
- new JsonSubTypes.Type(value = classOf[NestedPolymorphism1_2], name = "NestedPolymorphism1_2")
-))
-trait NestedPolymorphism1Base
-
-case class NestedPolymorphism1_1(a:String, pojo:NestedPolymorphism2Base) extends NestedPolymorphism1Base
-case class NestedPolymorphism1_2(a:String, pojo:NestedPolymorphism2Base) extends NestedPolymorphism1Base
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/ParentScala.scala b/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/ParentScala.scala
deleted file mode 100644
index 5352559..0000000
--- a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/ParentScala.scala
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.kjetland.jackson.jsonSchema.testDataScala
-
-import com.fasterxml.jackson.annotation.{JsonSubTypes, JsonTypeInfo}
-
-@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
-@JsonSubTypes(Array(new JsonSubTypes.Type(value = classOf[Child1Scala], name = "child1"), new JsonSubTypes.Type(value = classOf[Child2Scala], name = "child2")))
-trait ParentScala
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/PojoUsingOptionScala.scala b/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/PojoUsingOptionScala.scala
deleted file mode 100644
index 8d75041..0000000
--- a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/PojoUsingOptionScala.scala
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.kjetland.jackson.jsonSchema.testDataScala
-
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize
-
-case class PojoUsingOptionScala(
- _string:Option[String],
- @JsonDeserialize(contentAs = classOf[Int]) _integer:Option[Int],
- @JsonDeserialize(contentAs = classOf[Boolean]) _boolean:Option[Boolean],
- @JsonDeserialize(contentAs = classOf[Double]) _double:Option[Double],
- child1:Option[Child1Scala],
- optionalList:Option[List[ClassNotExtendingAnythingScala]]
- //, parent:Option[ParentScala] - Not using this one: jackson-scala-module does not support Option combined with Polymorphism
- )
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/PojoWithArraysScala.scala b/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/PojoWithArraysScala.scala
deleted file mode 100644
index b9cefa3..0000000
--- a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/PojoWithArraysScala.scala
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.kjetland.jackson.jsonSchema.testDataScala
-
-import com.kjetland.jackson.jsonSchema.testData.polymorphism1.Parent
-import javax.validation.constraints.{NotNull, Size}
-import com.kjetland.jackson.jsonSchema.testData.{ClassNotExtendingAnything, MyEnum}
-
-case class PojoWithArraysScala
-(
- @NotNull
- intArray1:Option[List[Integer]], // We never use array in scala - use list instead to make it compatible with PojoWithArrays (java)
- @NotNull
- stringArray:List[String], // We never use array in scala - use list instead to make it compatible with PojoWithArrays (java)
- @NotNull
- @Size(min = 1, max = 10)
- stringList:List[String],
- @NotNull
- polymorphismList:List[Parent],
- @NotNull
- polymorphismArray:List[Parent], // We never use array in scala - use list instead to make it compatible with PojoWithArrays (java)
- @NotNull
- regularObjectList:List[ClassNotExtendingAnything],
- @NotNull
- listOfListOfStrings:List[List[String]],
- @NotNull
- setOfUniqueValues:Set[MyEnum]
-)
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/PojoWithParentScala.scala b/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/PojoWithParentScala.scala
deleted file mode 100644
index 1c65755..0000000
--- a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/PojoWithParentScala.scala
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.kjetland.jackson.jsonSchema.testDataScala
-
-import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaDefault
-
-case class PojoWithParentScala
-(
- pojoValue:Boolean,
- child:ParentScala,
-
- @JsonSchemaDefault("x")
- stringWithDefault:String,
- @JsonSchemaDefault("12")
- intWithDefault:Int,
- @JsonSchemaDefault("true")
- booleanWithDefault:Boolean
-)
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/PolymorphismAndTitle.scala b/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/PolymorphismAndTitle.scala
deleted file mode 100644
index bff348f..0000000
--- a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/PolymorphismAndTitle.scala
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.kjetland.jackson.jsonSchema.testDataScala
-
-import com.fasterxml.jackson.annotation.{JsonSubTypes, JsonTypeInfo}
-import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle
-
-@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
-@JsonSubTypes(Array(
- new JsonSubTypes.Type(value = classOf[PolymorphismAndTitle1], name = "type_1"),
- new JsonSubTypes.Type(value = classOf[PolymorphismAndTitle2], name = "type_2")))
-trait PolymorphismAndTitleBase
-
-@JsonSchemaTitle("CustomTitle1")
-case class PolymorphismAndTitle1(a:String) extends PolymorphismAndTitleBase
-
-case class PolymorphismAndTitle2(a:String) extends PolymorphismAndTitleBase
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/PolymorphismOrdering.scala b/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/PolymorphismOrdering.scala
deleted file mode 100755
index e60dbc8..0000000
--- a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/PolymorphismOrdering.scala
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.kjetland.jackson.jsonSchema.testDataScala
-
-import com.fasterxml.jackson.annotation.{JsonSubTypes, JsonTypeInfo}
-
-
-@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
-@JsonSubTypes(Array(
- new JsonSubTypes.Type(value = classOf[PolymorphismOrderingChild3], name = "PolymorphismOrderingChild3"),
- new JsonSubTypes.Type(value = classOf[PolymorphismOrderingChild1], name = "PolymorphismOrderingChild1"),
- new JsonSubTypes.Type(value = classOf[PolymorphismOrderingChild4], name = "PolymorphismOrderingChild4"),
- new JsonSubTypes.Type(value = classOf[PolymorphismOrderingChild2], name = "PolymorphismOrderingChild2")))
-trait PolymorphismOrderingParentScala
-
-case class PolymorphismOrderingChild1() extends PolymorphismOrderingParentScala
-case class PolymorphismOrderingChild2() extends PolymorphismOrderingParentScala
-case class PolymorphismOrderingChild3() extends PolymorphismOrderingParentScala
-case class PolymorphismOrderingChild4() extends PolymorphismOrderingParentScala
-
-
-
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/UsingJsonSchemaInject.scala b/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/UsingJsonSchemaInject.scala
deleted file mode 100755
index 9943f2e..0000000
--- a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/UsingJsonSchemaInject.scala
+++ /dev/null
@@ -1,105 +0,0 @@
-package com.kjetland.jackson.jsonSchema.testDataScala
-
-import java.util.function.Supplier
-import javax.validation.constraints.{Min, Pattern}
-
-import com.fasterxml.jackson.databind.{JsonNode, ObjectMapper}
-import com.fasterxml.jackson.databind.node.{ArrayNode, ObjectNode}
-import com.kjetland.jackson.jsonSchema.annotations.{JsonSchemaBool, JsonSchemaInject, JsonSchemaInt, JsonSchemaString}
-
-import scala.annotation.meta.field
-
-
-@JsonSchemaInject(
- json=
- """
- {
- "patternProperties": {
- "^s[a-zA-Z0-9]+": {
- "type": "string"
- }
- },
- "properties": {
- "injectedInProperties": "true"
- }
- }
- """,
- strings = Array(new JsonSchemaString(path = "patternProperties/^i[a-zA-Z0-9]+/type", value = "integer"))
-)
-case class UsingJsonSchemaInject
-(
- @JsonSchemaInject(
- json=
- """
- {
- "options": {
- "hidden": true
- }
- }
- """)
- sa:String,
-
- @JsonSchemaInject(
- json=
- """
- {
- "type": "integer",
- "default": 12
- }
- """,
- merge = false
- )
- @Pattern(regexp = "xxx") // Should not end up in schema since we're replacing with injected
- saMergeFalse:String,
-
- @JsonSchemaInject(
- bools = Array(new JsonSchemaBool(path = "exclusiveMinimum", value = true)),
- ints = Array(new JsonSchemaInt(path = "multipleOf", value = 7))
- )
- @Min(5)
- ib:Int,
-
- @JsonSchemaInject(jsonSupplier = classOf[UserNamesLoader])
- uns:Set[String],
-
- @JsonSchemaInject(jsonSupplierViaLookup = "myCustomUserNamesLoader")
- uns2:Set[String]
-)
-
-class UserNamesLoader extends Supplier[JsonNode] {
- val _objectMapper = new ObjectMapper()
-
- override def get(): JsonNode = {
- val schema = _objectMapper.createObjectNode()
- val values = schema.putObject("items").putArray("enum")
- values.add("foo")
- values.add("bar")
-
- schema
- }
-}
-
-class CustomUserNamesLoader(custom:String) extends Supplier[JsonNode] {
- val _objectMapper = new ObjectMapper()
-
- override def get(): JsonNode = {
- val schema = _objectMapper.createObjectNode()
- val values = schema.putObject("items").putArray("enum")
- values.add("foo_"+custom)
- values.add("bar_"+custom)
-
- schema
- }
-}
-
-@JsonSchemaInject(
- json =
- """{
- "everything": "should be replaced"
- }""",
- merge = false
-)
-case class UsingJsonSchemaInjectWithTopLevelMergeFalse
-(
- shouldBeIgnored:String
-)
diff --git a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/UsingJsonSchemaOptions.scala b/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/UsingJsonSchemaOptions.scala
deleted file mode 100644
index e4ef9b9..0000000
--- a/src/test/scala/com/kjetland/jackson/jsonSchema/testDataScala/UsingJsonSchemaOptions.scala
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.kjetland.jackson.jsonSchema.testDataScala
-
-import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaOptions
-import com.fasterxml.jackson.annotation.{JsonSubTypes, JsonTypeInfo}
-
-
-@JsonSchemaOptions( items = Array(
- new JsonSchemaOptions.Item(name = "classOption", value="classOptionValue")))
-case class UsingJsonSchemaOptions
-(
- @JsonSchemaOptions( items = Array(
- new JsonSchemaOptions.Item(name = "o1", value="v1")))
- propertyUsingOneProperty:String
-
-)
-
-
-@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
-@JsonSubTypes(Array(
-new JsonSubTypes.Type(value = classOf[UsingJsonSchemaOptionsChild1], name = "c1"),
-new JsonSubTypes.Type(value = classOf[UsingJsonSchemaOptionsChild2], name = "c2")))
-trait UsingJsonSchemaOptionsBase
-
-
-@JsonSchemaOptions( items = Array(
- new JsonSchemaOptions.Item(name = "classOption1", value="classOptionValue1")))
-case class UsingJsonSchemaOptionsChild1
-(
- @JsonSchemaOptions( items = Array(
- new JsonSchemaOptions.Item(name = "o1", value="v1")))
- propertyUsingOneProperty:String
-
-)
-
-@JsonSchemaOptions( items = Array(
- new JsonSchemaOptions.Item(name = "classOption2", value="classOptionValue2")))
-case class UsingJsonSchemaOptionsChild2
-(
- @JsonSchemaOptions( items = Array(
- new JsonSchemaOptions.Item(name = "o1", value="v1")))
- propertyUsingOneProperty:String
-
-)
-
-
diff --git a/version.sbt b/version.sbt
deleted file mode 100644
index cc157e2..0000000
--- a/version.sbt
+++ /dev/null
@@ -1 +0,0 @@
-version in ThisBuild := "1.0.40-SNAPSHOT"