Repository: avro-kotlin/avro4k
Pull request #125 (2022-01-06 06:27:58)

Remove sealed interfaces
As described in #107 support for sealed interfaces will be removed with this PR. 

As a temporary workaround until sealed interfaces are officially supported in kotlinx.serialization one can use the new provided functions `polymorphicForSealed` and `polymorphicTreeForSealed`. These methods will be removed from the codebase once sealed interfaces are officially supported.

All dependencies are updated to the newest version.


Patch:
diff --git a/README.md b/README.md
index fcdbb01..1c5bd6f 100644
--- a/README.md
+++ b/README.md
@@ -430,38 +430,38 @@ Therefore some serializers will take into account the schema passed to them when
 The following table shows how types used in your code will be mapped / encoded in the generated Avro schemas and files. 
 If a type can be mapped in multiple ways, it is listed more than once.
 
-| JVM Type                   	| Schema Type   	| Logical Type     	| Encoded Type |
-|------------------------------	|---------------	|------------------	| ------------ |
-| String                       	| STRING        	|                  	| Utf8                      |
-| String                       	| FIXED        	    |                  	| GenericFixed              |
-| String                       	| BYTES        	    |                  	| ByteBuffer                |
-| Boolean                      	| BOOLEAN       	|                  	| java.lang.Boolean |
-| Long                         	| LONG          	|                  	| java.lang.Long |
-| Int                          	| INT           	|                  	| java.lang.Integer |
-| Short                        	| INT           	|                  	| java.lang.Integer |
-| Byte                         	| INT           	|                  	| java.lang.Integer |
-| Double                       	| DOUBLE        	|                  	| java.lang.Double |
-| Float                        	| FLOAT         	|                  	| java.lang.Float |
-| UUID                         	| STRING        	| UUID             	| Utf8 |
-| LocalDate                    	| INT           	| Date             	| java.lang.Int |
-| LocalTime                    	| INT           	| Time-Millis      	| java.lang.Int |
-| LocalDateTime                	| LONG          	| Timestamp-Millis 	| java.lang.Long |
-| Instant                      	| LONG          	| Timestamp-Millis 	| java.lang.Long |
-| Timestamp                    	| LONG          	| Timestamp-Millis 	| java.lang.Long |
-| BigDecimal                   	| BYTES         	| Decimal<8,2>     	| ByteBuffer |
-| BigDecimal                   	| FIXED         	| Decimal<8,2>     	| GenericFixed |
-| BigDecimal                   	| STRING         	| Decimal<8,2>     	| String |
-| T? (nullable type)           	| UNION<null,T> 	|                  	| null, T |
-| ByteArray                  	| BYTES         	|                  	| ByteBuffer |
-| ByteAray                  	| FIXED         	|                  	| GenericFixed |
-| ByteBuffer                   	| BYTES         	|                  	| ByteBuffer |
-| List[Byte]                   	| BYTES         	|                  	| ByteBuffer |
-| Array<T>                     	| ARRAY<T>      	|                  	| Array[T] |
-| List<T>                      	| ARRAY<T>      	|                  	| Array[T] |
-| Set<T>                      	| ARRAY<T>      	|                  	| Array[T] |
-| Map[String, V]              	| MAP<V>        	|                  	| java.util.Map[String, V] |
-| data class T                 	| RECORD        	|                  	| GenericRecord |
-| enum class             	    | ENUM          	|                  	| GenericEnumSymbol |
+| JVM Type                   	   | Schema Type   	  | Logical Type     	 | Encoded Type             |
+|--------------------------------|------------------|--------------------|--------------------------|
+| String                       	 | STRING        	  | 	                  | Utf8                     |
+| String                       	 | FIXED        	   | 	                  | GenericFixed             |
+| String                       	 | BYTES        	   | 	                  | ByteBuffer               |
+| Boolean                      	 | BOOLEAN       	  | 	                  | java.lang.Boolean        |
+| Long                         	 | LONG          	  | 	                  | java.lang.Long           |
+| Int                          	 | INT           	  | 	                  | java.lang.Integer        |
+| Short                        	 | INT           	  | 	                  | java.lang.Integer        |
+| Byte                         	 | INT           	  | 	                  | java.lang.Integer        |
+| Double                       	 | DOUBLE        	  | 	                  | java.lang.Double         |
+| Float                        	 | FLOAT         	  | 	                  | java.lang.Float          |
+| UUID                         	 | STRING        	  | UUID             	 | Utf8                     |
+| LocalDate                    	 | INT           	  | Date             	 | java.lang.Int            |
+| LocalTime                    	 | INT           	  | Time-Millis      	 | java.lang.Int            |
+| LocalDateTime                	 | LONG          	  | Timestamp-Millis 	 | java.lang.Long           |
+| Instant                      	 | LONG          	  | Timestamp-Millis 	 | java.lang.Long           |
+| Timestamp                    	 | LONG          	  | Timestamp-Millis 	 | java.lang.Long           |
+| BigDecimal                   	 | BYTES         	  | Decimal<8,2>     	 | ByteBuffer               |
+| BigDecimal                   	 | FIXED         	  | Decimal<8,2>     	 | GenericFixed             |
+| BigDecimal                   	 | STRING         	 | Decimal<8,2>     	 | String                   |
+| T? (nullable type)           	 | UNION<null,T> 	  | 	                  | null, T                  |
+| ByteArray                  	   | BYTES         	  | 	                  | ByteBuffer               |
+| ByteAray                  	    | FIXED         	  | 	                  | GenericFixed             |
+| ByteBuffer                   	 | BYTES         	  | 	                  | ByteBuffer               |
+| List[Byte]                   	 | BYTES         	  | 	                  | ByteBuffer               |
+| Array<T>                     	 | ARRAY<T>      	  | 	                  | Array[T]                 |
+| List<T>                      	 | ARRAY<T>      	  | 	                  | Array[T]                 |
+| Set<T>                      	  | ARRAY<T>      	  | 	                  | Array[T]                 |
+| Map[String, V]              	  | MAP<V>        	  | 	                  | java.util.Map[String, V] |
+| data class T                 	 | RECORD        	  | 	                  | GenericRecord            |
+| enum class             	       | ENUM          	  | 	                  | GenericEnumSymbol        |
 
 In order to use logical types, annotate the value with an appropriate Serializer:
 
diff --git a/build.gradle.kts b/build.gradle.kts
index f636630..0aeec3a 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -126,12 +126,12 @@ publishing {
          pom {
             name.set("avro4k-core")
             description.set("Avro format support for kotlinx.serialization")
-            url.set("http://www.github.com/avro-kotlin/avro4k")
+            url.set("https://www.github.com/avro-kotlin/avro4k")
 
             scm {
-               connection.set("scm:git:http://www.github.com/avro-kotlin/avro4k")
-               developerConnection.set("scm:git:http://github.com/avro-kotlin/avro4k")
-               url.set("http://www.github.com/avro-kotlin/avro4k")
+               connection.set("scm:git:https://www.github.com/avro-kotlin/avro4k")
+               developerConnection.set("scm:git:https://github.com/avro-kotlin/avro4k")
+               url.set("https://www.github.com/avro-kotlin/avro4k")
             }
 
             licenses {
diff --git a/buildSrc/src/main/kotlin/Libs.kt b/buildSrc/src/main/kotlin/Libs.kt
index aa6d7c1..06f664e 100644
--- a/buildSrc/src/main/kotlin/Libs.kt
+++ b/buildSrc/src/main/kotlin/Libs.kt
@@ -1,12 +1,12 @@
 object Libs {
 
-   const val kotlinVersion = "1.5.32"
+   const val kotlinVersion = "1.6.10"
    // const val dokkaVersion = "1.4.20"
-   const val kotestGradlePlugin = "0.3.8"
-   const val versionsPlugin = "0.39.0"
+   const val kotestGradlePlugin = "0.3.9"
+   const val versionsPlugin = "0.41.0"
 
    object Kotest {
-      private const val version = "4.6.3"
+      private const val version = "5.0.3"
       const val assertionsCore = "io.kotest:kotest-assertions-core:$version"
       const val assertionsJson = "io.kotest:kotest-assertions-json:$version"
       const val junit5 = "io.kotest:kotest-runner-junit5:$version"
@@ -14,7 +14,7 @@ object Libs {
    }
 
    object Kotlinx {
-      private const val version = "1.3.1"
+      private const val version = "1.3.2"
       const val serializationCore = "org.jetbrains.kotlinx:kotlinx-serialization-core:$version"
       const val serializationJson = "org.jetbrains.kotlinx:kotlinx-serialization-json:$version"
    }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 84d1f85..2e6e589 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/src/main/kotlin/com/github/avrokotlin/avro4k/SerialDescriptor.kt b/src/main/kotlin/com/github/avrokotlin/avro4k/SerialDescriptor.kt
index d5e4c39..52236ce 100644
--- a/src/main/kotlin/com/github/avrokotlin/avro4k/SerialDescriptor.kt
+++ b/src/main/kotlin/com/github/avrokotlin/avro4k/SerialDescriptor.kt
@@ -3,8 +3,6 @@ package com.github.avrokotlin.avro4k
 import kotlinx.serialization.ExperimentalSerializationApi
 import kotlinx.serialization.descriptors.*
 import kotlinx.serialization.modules.SerializersModule
-import kotlinx.serialization.serializer
-import kotlin.reflect.full.starProjectedType
 
 @ExperimentalSerializationApi
 fun SerialDescriptor.possibleSerializationSubclasses(serializersModule: SerializersModule): List<SerialDescriptor> {
@@ -13,18 +11,9 @@ fun SerialDescriptor.possibleSerializationSubclasses(serializersModule: Serializ
         PolymorphicKind.SEALED -> elementDescriptors.filter { it.kind == SerialKind.CONTEXTUAL }
             .flatMap { it.elementDescriptors }
             .flatMap { it.possibleSerializationSubclasses(serializersModule) }
-        PolymorphicKind.OPEN -> {
-            val capturedClass = this.capturedKClass
-            if (capturedClass?.isSealed == true) {
-                //A sealed interface will have the kind PolymorphicKind.OPEN, although it is sealed. Retrieve all the possible direct subclasses of the interface
-                capturedClass.sealedSubclasses.map { serializersModule.serializer(it.starProjectedType) }
-                    .map { it.descriptor }
-                    .flatMap { it.possibleSerializationSubclasses(serializersModule) }
-            } else {
-                serializersModule.getPolymorphicDescriptors(this)
-                    .flatMap { it.possibleSerializationSubclasses(serializersModule) }
-            }
-        }
+        PolymorphicKind.OPEN ->
+            serializersModule.getPolymorphicDescriptors(this)
+                .flatMap { it.possibleSerializationSubclasses(serializersModule) }
         else -> throw UnsupportedOperationException("Can't get possible serialization subclasses for the SerialDescriptor of kind ${this.kind}.")
     }
 }
diff --git a/src/main/kotlin/com/github/avrokotlin/avro4k/SerializersModuleBuilder.kt b/src/main/kotlin/com/github/avrokotlin/avro4k/SerializersModuleBuilder.kt
new file mode 100644
index 0000000..7abc559
--- /dev/null
+++ b/src/main/kotlin/com/github/avrokotlin/avro4k/SerializersModuleBuilder.kt
@@ -0,0 +1,43 @@
+@file:OptIn(InternalSerializationApi::class)
+@file:Suppress("UNCHECKED_CAST")
+package com.github.avrokotlin.avro4k
+import kotlinx.serialization.InternalSerializationApi
+import kotlinx.serialization.modules.SerializersModuleBuilder
+import kotlinx.serialization.serializer
+import kotlin.reflect.KClass
+
+/**
+ * Registers all direct subclasses of a sealed type as polymorphic types.
+ *
+ * Warning: This method will be removed with no further warning as soon as kotlinx.serialization natively supports
+ * sealed interfaces.
+ */
+fun <T : Any> SerializersModuleBuilder.polymorphicForSealed(sealedType: KClass<T>) {
+    if (!sealedType.isSealed) throw IllegalArgumentException(
+        "Type $sealedType is not sealed."
+    )
+    sealedType.sealedSubclasses.forEach {
+        polymorphic(sealedType, it as KClass<T>, it.serializer())
+    }
+}
+
+
+/**
+ * Registers all subclasses of a sealed type tree as polymorphic types. In contrast to polymorphicForSealed this method
+ * also adds subclasses of sealed subclasses as polymorphic types.
+ *
+ * Warning: This method will be removed with no further warning as soon as kotlinx.serialization natively supports
+ * sealed interfaces.
+ */
+fun <T : Any> SerializersModuleBuilder.polymorphicTreeForSealed(sealedType: KClass<T>) {
+    if (!sealedType.isSealed) throw IllegalArgumentException(
+        "Type $sealedType is not a sealed type."
+    )
+    sealedType.sealedSubclasses.forEach {
+        polymorphic(sealedType, it as KClass<T>, it.serializer())
+        if(it.isSealed) {
+            polymorphicTreeForSealed(it)
+        }
+    }
+}
+
diff --git a/src/main/kotlin/com/github/avrokotlin/avro4k/io/AvroDataInputStream.kt b/src/main/kotlin/com/github/avrokotlin/avro4k/io/AvroDataInputStream.kt
index b6b177e..d8b6b17 100644
--- a/src/main/kotlin/com/github/avrokotlin/avro4k/io/AvroDataInputStream.kt
+++ b/src/main/kotlin/com/github/avrokotlin/avro4k/io/AvroDataInputStream.kt
@@ -19,7 +19,7 @@ class AvroDataInputStream<T>(private val source: InputStream,
       else -> GenericData.get().createDatumReader(writerSchema, readerSchema)
    }
 
-   private val dataFileReader = DataFileStream<Any>(source, datumReader)
+   private val dataFileReader = DataFileStream(source, datumReader)
 
    override fun next(): T? {
       return if (dataFileReader.hasNext()) {
diff --git a/src/main/kotlin/com/github/avrokotlin/avro4k/io/AvroDecodeFormat.kt b/src/main/kotlin/com/github/avrokotlin/avro4k/io/AvroDecodeFormat.kt
index 83ec0a4..40e8492 100644
--- a/src/main/kotlin/com/github/avrokotlin/avro4k/io/AvroDecodeFormat.kt
+++ b/src/main/kotlin/com/github/avrokotlin/avro4k/io/AvroDecodeFormat.kt
@@ -19,7 +19,7 @@ sealed class AvroDecodeFormat {
                 writerSchema != null && readerSchema != null ->
                     AvroDataInputStream(source, converter, writerSchema, readerSchema)
                 writerSchema != null ->
-                    AvroDataInputStream(source, converter, writerSchema, readerSchema)
+                    AvroDataInputStream(source, converter, writerSchema, null)
                 readerSchema != null ->
                     AvroDataInputStream(source, converter, null, readerSchema)
                 else ->
diff --git a/src/main/kotlin/com/github/avrokotlin/avro4k/schema/schemas.kt b/src/main/kotlin/com/github/avrokotlin/avro4k/schema/schemas.kt
index bd987b5..4239291 100644
--- a/src/main/kotlin/com/github/avrokotlin/avro4k/schema/schemas.kt
+++ b/src/main/kotlin/com/github/avrokotlin/avro4k/schema/schemas.kt
@@ -12,14 +12,6 @@ fun createSafeUnion(nullFirst : Boolean,vararg schemas: Schema): Schema {
    return Schema.createUnion(if(nullFirst) nulls + rest else rest + nulls)
 }
 
-fun Schema.findSubschema(name: String): Schema? {
-   require(type == Schema.Type.RECORD)
-   return fields.find { it.name() == name }?.schema()
-}
-
-fun Schema.containsNull(): Boolean =
-   type == Schema.Type.UNION && types.any { it.type == Schema.Type.NULL }
-
 fun Schema.extractNonNull(): Schema = when (this.type) {
    Schema.Type.UNION -> this.types.filter { it.type != Schema.Type.NULL }.let { if(it.size > 1) Schema.createUnion(it) else it[0] }
    else -> this
diff --git a/src/main/kotlin/com/github/avrokotlin/avro4k/serializer/BigDecimalSerializer.kt b/src/main/kotlin/com/github/avrokotlin/avro4k/serializer/BigDecimalSerializer.kt
index 2a9389c..40f21ca 100644
--- a/src/main/kotlin/com/github/avrokotlin/avro4k/serializer/BigDecimalSerializer.kt
+++ b/src/main/kotlin/com/github/avrokotlin/avro4k/serializer/BigDecimalSerializer.kt
@@ -29,7 +29,7 @@ class BigDecimalSerializer : AvroSerializer<BigDecimal>() {
    override val descriptor: SerialDescriptor = object : AvroDescriptor(BigDecimal::class.jvmName, PrimitiveKind.BYTE) {
       override fun schema(annos: List<Annotation>, serializersModule: SerializersModule, namingStrategy: NamingStrategy): Schema {
          val schema = SchemaBuilder.builder().bytesType()
-         val (scale, precision) = AnnotationExtractor(annos).scalePrecision() ?: 2 to 8
+         val (scale, precision) = AnnotationExtractor(annos).scalePrecision() ?: (2 to 8)
          return LogicalTypes.decimal(precision, scale).addToSchema(schema)
       }
    }


Test patch:
diff --git a/src/test/kotlin/com/github/avrokotlin/avro4k/io/CollectionsIoTest.kt b/src/test/kotlin/com/github/avrokotlin/avro4k/io/CollectionsIoTest.kt
index e8fb26d..c0f6e8e 100644
--- a/src/test/kotlin/com/github/avrokotlin/avro4k/io/CollectionsIoTest.kt
+++ b/src/test/kotlin/com/github/avrokotlin/avro4k/io/CollectionsIoTest.kt
@@ -8,7 +8,7 @@ import io.kotest.property.Arb
 import io.kotest.property.arbitrary.double
 import io.kotest.property.arbitrary.int
 import io.kotest.property.arbitrary.long
-import io.kotest.property.arbitrary.numericDoubles
+import io.kotest.property.arbitrary.numericDouble
 import io.kotest.property.checkAll
 import kotlinx.serialization.Serializable
 import org.apache.avro.generic.GenericArray
@@ -63,9 +63,9 @@ class CollectionsIoTest : StringSpec({
    "read / write arrays of double in json" {
       //Json does not support -inf/+inf and NaN
       checkAll(
-         Arb.numericDoubles(),
-         Arb.numericDoubles(),
-         Arb.numericDoubles()
+         Arb.numericDouble(),
+         Arb.numericDouble(),
+         Arb.numericDouble()
       ) { a, b, c ->
          writeReadJson(DoubleArrayTest(arrayOf(a, b, c)), DoubleArrayTest.serializer()) {
             it["a"] shouldBe listOf(a,b,c)
diff --git a/src/test/kotlin/com/github/avrokotlin/avro4k/schema/AvroDefaultSchemaTest.kt b/src/test/kotlin/com/github/avrokotlin/avro4k/schema/AvroDefaultSchemaTest.kt
index fb4387d..be875c6 100644
--- a/src/test/kotlin/com/github/avrokotlin/avro4k/schema/AvroDefaultSchemaTest.kt
+++ b/src/test/kotlin/com/github/avrokotlin/avro4k/schema/AvroDefaultSchemaTest.kt
@@ -1,3 +1,5 @@
+@file:Suppress("unused")
+
 package com.github.avrokotlin.avro4k.schema
 
 import com.github.avrokotlin.avro4k.Avro
@@ -207,7 +209,7 @@ data class BarArray(
 
 @Serializable
 data class BarInvalidArrayType(
-   @com.github.avrokotlin.avro4k.AvroDefault("""["foo-bar"]""")
+   @AvroDefault("""["foo-bar"]""")
    val defaultFloatArrayWith2Defaults: List<Float>
 )
 
diff --git a/src/test/kotlin/com/github/avrokotlin/avro4k/schema/MixedPolymorphicSchemaTest.kt b/src/test/kotlin/com/github/avrokotlin/avro4k/schema/MixedPolymorphicSchemaTest.kt
index 346f095..98a80b0 100644
--- a/src/test/kotlin/com/github/avrokotlin/avro4k/schema/MixedPolymorphicSchemaTest.kt
+++ b/src/test/kotlin/com/github/avrokotlin/avro4k/schema/MixedPolymorphicSchemaTest.kt
@@ -2,6 +2,7 @@ package com.github.avrokotlin.avro4k.schema
 
 import com.github.avrokotlin.avro4k.Avro
 import com.github.avrokotlin.avro4k.AvroDefault
+import com.github.avrokotlin.avro4k.polymorphicTreeForSealed
 import io.kotest.core.spec.style.StringSpec
 import io.kotest.matchers.shouldBe
 import kotlinx.serialization.Serializable
@@ -47,6 +48,7 @@ data class ReferencingNullableMixedPolymorphic(
 )
 class MixedPolymorphicSchemaTest : StringSpec({
     val module = SerializersModule {
+        polymorphicTreeForSealed(Expr::class)
         polymorphic(OtherUnaryExpr::class) {
             subclass(ConstExpr::class)
         }
diff --git a/src/test/kotlin/com/github/avrokotlin/avro4k/schema/SealedClassSchemaTest.kt b/src/test/kotlin/com/github/avrokotlin/avro4k/schema/SealedClassSchemaTest.kt
index 9e53b54..7da0554 100644
--- a/src/test/kotlin/com/github/avrokotlin/avro4k/schema/SealedClassSchemaTest.kt
+++ b/src/test/kotlin/com/github/avrokotlin/avro4k/schema/SealedClassSchemaTest.kt
@@ -1,9 +1,11 @@
+@file:Suppress("unused")
+
 package com.github.avrokotlin.avro4k.schema
 
 import com.github.avrokotlin.avro4k.Avro
 import com.github.avrokotlin.avro4k.AvroDefault
-import io.kotest.matchers.shouldBe
 import io.kotest.core.spec.style.StringSpec
+import io.kotest.matchers.shouldBe
 import kotlinx.serialization.Serializable
 import org.apache.avro.Schema
 
@@ -13,14 +15,14 @@ sealed class Operation {
    object Nullary : Operation()
 
    @Serializable
-   sealed class Unary() : Operation(){
+   sealed class Unary : Operation(){
       abstract val value : Int
       @Serializable
       data class Negate(override val value:Int) : Unary()
    }
 
    @Serializable
-   sealed class Binary() : Operation(){
+   sealed class Binary : Operation(){
       abstract val left : Int
       abstract val right : Int
       @Serializable
diff --git a/src/test/kotlin/com/github/avrokotlin/avro4k/schema/SealedInterfaceSchemaTest.kt b/src/test/kotlin/com/github/avrokotlin/avro4k/schema/SealedInterfaceSchemaTest.kt
index f145906..610264b 100644
--- a/src/test/kotlin/com/github/avrokotlin/avro4k/schema/SealedInterfaceSchemaTest.kt
+++ b/src/test/kotlin/com/github/avrokotlin/avro4k/schema/SealedInterfaceSchemaTest.kt
@@ -1,10 +1,16 @@
+@file:Suppress("unused")
+
 package com.github.avrokotlin.avro4k.schema
 
 import com.github.avrokotlin.avro4k.Avro
 import com.github.avrokotlin.avro4k.AvroDefault
+import com.github.avrokotlin.avro4k.polymorphicForSealed
+import com.github.avrokotlin.avro4k.polymorphicTreeForSealed
 import io.kotest.core.spec.style.StringSpec
 import io.kotest.matchers.shouldBe
+import kotlinx.serialization.InternalSerializationApi
 import kotlinx.serialization.Serializable
+import kotlinx.serialization.modules.SerializersModule
 import org.apache.avro.Schema
 
 sealed interface Calculable
@@ -35,16 +41,29 @@ data class ReferencingNullableSealedInterface(
    val nullable : Calculable?
 )
 
+@OptIn(InternalSerializationApi::class)
 class SealedInterfaceSchemaTest : StringSpec({
 
    "referencing a sealed hierarchy"{
-      val schema = Avro.default.schema(ReferencingSealedInterface.serializer())
-      val expected = Schema.Parser().parse(javaClass.getResourceAsStream("/sealed_interface_referenced.json"))
+      val schema = Avro(serializersModule = SerializersModule {
+         polymorphicTreeForSealed(Calculable::class)
+      }).schema(ReferencingSealedInterface.serializer())
+      val expected = Schema.Parser().parse(javaClass.getResourceAsStream("/sealed_interface_hierarchy_referenced.json"))
       schema shouldBe expected
    }
    "referencing a nullable sealed hierarchy"{
-      val schema = Avro.default.schema(ReferencingNullableSealedInterface.serializer())
+      val schema = Avro(serializersModule = SerializersModule {
+         polymorphicTreeForSealed(Calculable::class)
+      }).schema(ReferencingNullableSealedInterface.serializer())
       val expected = Schema.Parser().parse(javaClass.getResourceAsStream("/sealed_interface_nullable_referenced.json"))
       schema shouldBe expected
    }
-})
\ No newline at end of file
+   "referencing a sealed interface"{
+      val schema = Avro(serializersModule = SerializersModule {
+         polymorphicForSealed(Calculable::class)
+      }).schema(ReferencingSealedInterface.serializer())
+      val expected = Schema.Parser().parse(javaClass.getResourceAsStream("/sealed_interface_referenced.json"))
+      schema shouldBe expected
+   }
+})
+
diff --git a/src/test/resources/sealed_interface_hierarchy_referenced.json b/src/test/resources/sealed_interface_hierarchy_referenced.json
new file mode 100644
index 0000000..fe60b98
--- /dev/null
+++ b/src/test/resources/sealed_interface_hierarchy_referenced.json
@@ -0,0 +1,55 @@
+{
+   "type":"record",
+   "name":"ReferencingSealedInterface",
+   "namespace":"com.github.avrokotlin.avro4k.schema",
+   "fields":[
+      {
+         "name":"notNullable",
+         "type":[
+            {
+               "type":"record",
+               "name":"AddCalculable",
+               "fields":[
+                  {
+                     "name":"left",
+                     "type":"int"
+                  },
+                  {
+                     "name":"right",
+                     "type":"int"
+                  }
+               ]
+            },
+            {
+               "type":"record",
+               "name":"NegateCalculable",
+               "fields":[
+                  {
+                     "name":"value",
+                     "type":"int"
+                  }
+               ]
+            },
+            {
+               "type":"record",
+               "name":"NullaryCalculable",
+               "fields":[]
+            },
+            {
+               "type":"record",
+               "name":"SubstractCalculable",
+               "fields":[
+                  {
+                     "name":"left",
+                     "type":"int"
+                  },
+                  {
+                     "name":"right",
+                     "type":"int"
+                  }
+               ]
+            }
+         ]
+      }
+   ]
+}
diff --git a/src/test/resources/sealed_interface_referenced.json b/src/test/resources/sealed_interface_referenced.json
index fe60b98..979b8fe 100644
--- a/src/test/resources/sealed_interface_referenced.json
+++ b/src/test/resources/sealed_interface_referenced.json
@@ -6,48 +6,10 @@
       {
          "name":"notNullable",
          "type":[
-            {
-               "type":"record",
-               "name":"AddCalculable",
-               "fields":[
-                  {
-                     "name":"left",
-                     "type":"int"
-                  },
-                  {
-                     "name":"right",
-                     "type":"int"
-                  }
-               ]
-            },
-            {
-               "type":"record",
-               "name":"NegateCalculable",
-               "fields":[
-                  {
-                     "name":"value",
-                     "type":"int"
-                  }
-               ]
-            },
             {
                "type":"record",
                "name":"NullaryCalculable",
                "fields":[]
-            },
-            {
-               "type":"record",
-               "name":"SubstractCalculable",
-               "fields":[
-                  {
-                     "name":"left",
-                     "type":"int"
-                  },
-                  {
-                     "name":"right",
-                     "type":"int"
-                  }
-               ]
             }
          ]
       }