Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: concrete superclass polymorphism #148

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
616 changes: 318 additions & 298 deletions src/main/scala/com/kjetland/jackson/jsonSchema/JsonSchemaGenerator.scala

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.kjetland.jackson.jsonSchema;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;

public class SubtypeOrderTest {

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(Line.class),
@JsonSubTypes.Type(Pt.class) })
public static class Loc {
}
@JsonSubTypes({
@JsonSubTypes.Type(Abs.class)
})
public static class Pt extends Loc {
public String somePayload;
}
public static class Abs extends Pt {
}
public static class Line extends Loc {
public List<Proxy> attr;
}
public static class Proxy {
public List<Pt> attr;
}

private final ObjectMapper MAPPER = new ObjectMapper();

public void testGenerateSchema() throws IOException {
com.kjetland.jackson.jsonSchema.JsonSchemaGenerator generator = new com.kjetland.jackson.jsonSchema.JsonSchemaGenerator(MAPPER);
ObjectWriter objectWriter = MAPPER.writerWithDefaultPrettyPrinter();
JsonNode jsonNode = generator.generateJsonSchema(Loc.class);
System.out.println(objectWriter.writeValueAsString(jsonNode));
String value = objectWriter.writeValueAsString(new Pt());
System.out.println(value);
Pt pt = MAPPER.readValue(value, Pt.class);
Line line = new Line();
Proxy proxy = new Proxy();
proxy.attr = Collections.singletonList(new Abs());
line.attr = Collections.singletonList(proxy);
value = objectWriter.writeValueAsString(line);
System.out.println(value);
line = MAPPER.readValue(value, Line.class);
}

public static void main(String[] args) throws IOException {
new SubtypeOrderTest().testGenerateSchema();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.kjetland.jackson.jsonSchema.testData.polymorphism3.{Child31, Child32,
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.testData.polymorphism7.{Child73, Parent7}
import com.kjetland.jackson.jsonSchema.testDataScala._
import com.kjetland.jackson.jsonSchema.testData_issue_24.EntityWrapper
import javax.validation.groups.Default
Expand Down Expand Up @@ -329,8 +330,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {
assert(schema.at("/properties/pojoValue/type").asText() == "boolean")
assertDefaultValues(schema)

assertChild1(schema, "/properties/child/oneOf")
assertChild2(schema, "/properties/child/oneOf")
assertChild1(schema, "/properties/child/anyOf")
assertChild2(schema, "/properties/child/anyOf")
}

// Java - html5
Expand All @@ -342,8 +343,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {
assert(schema.at("/properties/pojoValue/type").asText() == "boolean")
assertDefaultValues(schema)

assertChild1(schema, "/properties/child/oneOf", html5Checks = true)
assertChild2(schema, "/properties/child/oneOf", html5Checks = true)
assertChild1(schema, "/properties/child/anyOf", html5Checks = true)
assertChild2(schema, "/properties/child/anyOf", html5Checks = true)
}

// Java - html5/nullable
Expand All @@ -355,8 +356,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {
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)
assertNullableChild1(schema, "/properties/child/oneOf/1/anyOf", html5Checks = true)
assertNullableChild2(schema, "/properties/child/oneOf/1/anyOf", html5Checks = true)
}

//Using fully-qualified class names
Expand All @@ -368,8 +369,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {
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")
assertChild1(schema, "/properties/child/anyOf", "com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child1")
assertChild2(schema, "/properties/child/anyOf", "com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child2")
}

// Using fully-qualified class names and nullable types
Expand All @@ -381,8 +382,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {
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")
assertNullableChild1(schema, "/properties/child/oneOf/1/anyOf", "com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child1")
assertNullableChild2(schema, "/properties/child/oneOf/1/anyOf", "com.kjetland.jackson.jsonSchema.testData.polymorphism1.Child2")
}

// Scala
Expand All @@ -394,8 +395,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {
assert(schema.at("/properties/pojoValue/type").asText() == "boolean")
assertDefaultValues(schema)

assertChild1(schema, "/properties/child/oneOf", "Child1Scala")
assertChild2(schema, "/properties/child/oneOf", "Child2Scala")
assertChild1(schema, "/properties/child/anyOf", "Child1Scala")
assertChild2(schema, "/properties/child/anyOf", "Child2Scala")
}
}

Expand Down Expand Up @@ -453,8 +454,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {

val schema = generateAndValidateSchema(jsonSchemaGenerator, classOf[Parent], Some(jsonNode))

assertChild1(schema, "/oneOf")
assertChild2(schema, "/oneOf")
assertChild1(schema, "/anyOf")
assertChild2(schema, "/anyOf")
}

// Java + Nullables
Expand All @@ -464,8 +465,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {

val schema = generateAndValidateSchema(jsonSchemaGenerator, classOf[Parent], Some(jsonNode))

assertChild1(schema, "/oneOf")
assertChild2(schema, "/oneOf")
assertChild1(schema, "/anyOf")
assertChild2(schema, "/anyOf")
}

// Scala
Expand All @@ -475,8 +476,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {

val schema = generateAndValidateSchema(jsonSchemaGeneratorScala, classOf[ParentScala], Some(jsonNode))

assertChild1(schema, "/oneOf", "Child1Scala")
assertChild2(schema, "/oneOf", "Child2Scala")
assertChild1(schema, "/anyOf", "Child1Scala")
assertChild2(schema, "/anyOf", "Child2Scala")
}

}
Expand All @@ -494,8 +495,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {

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")
assertChild1(schema, "/anyOf", "Child21", typeParamName = "clazz", typeName = "com.kjetland.jackson.jsonSchema.testData.polymorphism2.Child21")
assertChild2(schema, "/anyOf", "Child22", typeParamName = "clazz", typeName = "com.kjetland.jackson.jsonSchema.testData.polymorphism2.Child22")
}
}

Expand All @@ -512,11 +513,11 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {

val schema = generateAndValidateSchema(g, classOf[Parent5], Some(jsonNode))

assertChild1(schema, "/oneOf", "Child51", typeParamName = "clazz", typeName = ".Child51")
assertChild2(schema, "/oneOf", "Child52", typeParamName = "clazz", typeName = ".Child52")
assertChild1(schema, "/anyOf", "Child51", typeParamName = "clazz", typeName = ".Child51")
assertChild2(schema, "/anyOf", "Child52", typeParamName = "clazz", typeName = ".Child52")

val embeddedTypeName = _objectMapper.valueToTree[ObjectNode](new Parent5.Child51InnerClass()).get("clazz").asText()
assertChild1(schema, "/oneOf", "Child51InnerClass", typeParamName = "clazz", typeName = embeddedTypeName)
assertChild1(schema, "/anyOf", "Child51InnerClass", typeParamName = "clazz", typeName = embeddedTypeName)
}
}

Expand All @@ -532,8 +533,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {

val schema = generateAndValidateSchema(g, classOf[Parent6], Some(jsonNode))

assertChild1(schema, "/oneOf", "Child61", typeParamName = "clazz", typeName = ".Child61")
assertChild2(schema, "/oneOf", "Child62", typeParamName = "clazz", typeName = ".Child62")
assertChild1(schema, "/anyOf", "Child61", typeParamName = "clazz", typeName = ".Child61")
assertChild2(schema, "/anyOf", "Child62", typeParamName = "clazz", typeName = ".Child62")
}
}

Expand All @@ -547,8 +548,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {

val schema = generateAndValidateSchema(jsonSchemaGenerator, classOf[Parent3], Some(jsonNode))

assertChild1(schema, "/oneOf", "Child31", typeName = "child31")
assertChild2(schema, "/oneOf", "Child32", typeName = "child32")
assertChild1(schema, "/anyOf", "Child31", typeName = "child31")
assertChild2(schema, "/anyOf", "Child32", typeName = "child32")
}
}

Expand All @@ -568,6 +569,20 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {
}
}

test("Generate schema for multiple levels of type hierarchy") {

// Java
{
val config = JsonSchemaConfig.vanillaJsonSchemaDraft4
val g = new JsonSchemaGenerator(_objectMapper, debug = true, config)

val jsonNode = assertToFromJson(g, testData.child73)
assertToFromJson(g, testData.child73, classOf[Parent7])

val schema = generateAndValidateSchema(g, classOf[Parent7], Some(jsonNode))
}
}

test("Generate schema for class containing generics with same base type but different type arguments") {
{
val config = JsonSchemaConfig.vanillaJsonSchemaDraft4
Expand Down Expand Up @@ -792,12 +807,12 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {
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)
assertChild1(schema, "/properties/polymorphismList/items/anyOf", html5Checks = html5Checks)
assertChild2(schema, "/properties/polymorphismList/items/anyOf", 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)
assertChild1(schema, "/properties/polymorphismArray/items/anyOf", html5Checks = html5Checks)
assertChild2(schema, "/properties/polymorphismArray/items/anyOf", html5Checks = html5Checks)

assert(schema.at("/properties/listOfListOfStrings/type").asText() == "array")
assert(schema.at("/properties/listOfListOfStrings/items/type").asText() == "array")
Expand Down Expand Up @@ -832,12 +847,12 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {
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")
assertNullableChild1(schema, "/properties/polymorphismList/oneOf/1/items/anyOf")
assertNullableChild2(schema, "/properties/polymorphismList/oneOf/1/items/anyOf")

assertNullableType(schema, "/properties/polymorphismArray", "array")
assertNullableChild1(schema, "/properties/polymorphismArray/oneOf/1/items/oneOf")
assertNullableChild2(schema, "/properties/polymorphismArray/oneOf/1/items/oneOf")
assertNullableChild1(schema, "/properties/polymorphismArray/oneOf/1/items/anyOf")
assertNullableChild2(schema, "/properties/polymorphismArray/oneOf/1/items/anyOf")

assertNullableType(schema, "/properties/listOfListOfStrings", "array")
assert(schema.at("/properties/listOfListOfStrings/oneOf/1/items/type").asText() == "array")
Expand Down Expand Up @@ -893,8 +908,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {
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")
assert(schema.at("/properties/string2PojoUsingJsonTypeInfo/additionalProperties/anyOf/0/$ref").asText() == "#/definitions/Child1")
assert(schema.at("/properties/string2PojoUsingJsonTypeInfo/additionalProperties/anyOf/1/$ref").asText() == "#/definitions/Child2")
}

// Try it with nullable types.
Expand All @@ -909,8 +924,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {
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")
assert(schema.at("/properties/string2PojoUsingJsonTypeInfo/oneOf/1/additionalProperties/anyOf/0/$ref").asText() == "#/definitions/Child1")
assert(schema.at("/properties/string2PojoUsingJsonTypeInfo/oneOf/1/additionalProperties/anyOf/1/$ref").asText() == "#/definitions/Child2")
}
}

Expand Down Expand Up @@ -1404,8 +1419,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {

val schema = generateAndValidateSchema(jsonSchemaGenerator, classOf[MixinParent], Some(jsonNode))

assertChild1(schema, "/oneOf", defName = "MixinChild1")
assertChild2(schema, "/oneOf", defName = "MixinChild2")
assertChild1(schema, "/anyOf", defName = "MixinChild1")
assertChild2(schema, "/anyOf", defName = "MixinChild2")
}

// Java + Nullable types
Expand All @@ -1415,8 +1430,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {

val schema = generateAndValidateSchema(jsonSchemaGeneratorNullable, classOf[MixinParent], Some(jsonNode))

assertNullableChild1(schema, "/oneOf", defName = "MixinChild1")
assertNullableChild2(schema, "/oneOf", defName = "MixinChild2")
assertNullableChild1(schema, "/anyOf", defName = "MixinChild1")
assertNullableChild2(schema, "/anyOf", defName = "MixinChild2")
}
}

Expand All @@ -1425,10 +1440,10 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {
jsonSchemaGeneratorNullable.generateJsonSchema(classOf[EntityWrapper])
}

test("Polymorphism oneOf-ordering") {
test("Polymorphism anyOf-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)
val anyOfList:List[String] = schema.at("/anyOf").asInstanceOf[ArrayNode].iterator().asScala.toList.map(_.at("/$ref").asText)
assert(List("#/definitions/PolymorphismOrderingChild3", "#/definitions/PolymorphismOrderingChild1", "#/definitions/PolymorphismOrderingChild4", "#/definitions/PolymorphismOrderingChild2") == anyOfList)
}

test("@NotNull annotations and nullable types") {
Expand Down Expand Up @@ -1460,8 +1475,8 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {
println("--------------------------------------------")
println(asPrettyJson(schema, jsonSchemaGeneratorScala.rootObjectMapper))

assert( schema.at("/oneOf/0/$ref").asText() == "#/definitions/PolymorphismAndTitle1")
assert( schema.at("/oneOf/0/title").asText() == "CustomTitle1")
assert( schema.at("/anyOf/0/$ref").asText() == "#/definitions/PolymorphismAndTitle1")
assert( schema.at("/anyOf/0/title").asText() == "CustomTitle1")
}

test("UsingJsonSchemaOptions") {
Expand Down Expand Up @@ -1557,7 +1572,7 @@ class JsonSchemaGeneratorTest extends FunSuite with Matchers {
}

// PojoWithParent has a property of type Parent (which uses polymorphism).
// Default rendering schema will make this property oneOf Child1 and Child2.
// Default rendering schema will make this property anyOf 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

Expand Down Expand Up @@ -1794,6 +1809,15 @@ trait TestData {
c.child1String3 = "cs3"
c
}
val child73 = {
val c = new Child73()
c.parentString = "pv"
c.child1String = "cs"
c.child1String2 = "cs2"
c.child1String3 = "cs3"
c.child3String = "cs4"
c
}

val child2Scala = Child2Scala("pv", 12)
val child1Scala = Child1Scala("pv", "cs", "cs2", "cs3")
Expand Down
Loading