Repository: avro-kotlin/avro4k
Pull request #111 (2021-09-07 12:50:01)

Adding support for sealed interfaces and mixed polymorphic class structures


Patch:
diff --git a/README.md b/README.md
index 52f5c1e..80945c9 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,7 @@
 
 ![build-master](https://github.com/avro-kotlin/avro4k/workflows/build-master/badge.svg)
 [<img src="https://img.shields.io/maven-central/v/com.github.avro-kotlin.avro4k/avro4k-core.svg?label=latest%20release"/>](http://search.maven.org/#search%7Cga%7C1%7Cavro4k)
+[<img src="https://img.shields.io/nexus/s/https/oss.sonatype.org/com.github.avro-kotlin.avro4k/avro4k-core.svg?label=latest%20snapshot&style=plastic"/>](https://oss.sonatype.org/content/repositories/snapshots/com/github/avro-kotlin/avro4k/avro4k-core/)
 
 
 ## Introduction
diff --git a/buildSrc/src/main/kotlin/Libs.kt b/buildSrc/src/main/kotlin/Libs.kt
index 1800069..43a128c 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.20-RC"
+   const val kotlinVersion = "1.5.30"
    // const val dokkaVersion = "1.4.20"
    const val kotestGradlePlugin = "0.3.8"
-   const val versionsPlugin = "0.38.0"
+   const val versionsPlugin = "0.39.0"
 
    object Kotest {
-      private const val version = "4.3.1"
+      private const val version = "4.6.2"
       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.2.1"
+      private const val version = "1.2.2"
       const val serializationCore = "org.jetbrains.kotlinx:kotlinx-serialization-core:$version"
       const val serializationJson = "org.jetbrains.kotlinx:kotlinx-serialization-json:$version"
    }
diff --git a/src/main/kotlin/com/github/avrokotlin/avro4k/SerialDescriptor.kt b/src/main/kotlin/com/github/avrokotlin/avro4k/SerialDescriptor.kt
index 50eedb1..d5e4c39 100644
--- a/src/main/kotlin/com/github/avrokotlin/avro4k/SerialDescriptor.kt
+++ b/src/main/kotlin/com/github/avrokotlin/avro4k/SerialDescriptor.kt
@@ -3,22 +3,28 @@ package com.github.avrokotlin.avro4k
 import kotlinx.serialization.ExperimentalSerializationApi
 import kotlinx.serialization.descriptors.*
 import kotlinx.serialization.modules.SerializersModule
-
-@ExperimentalSerializationApi
-fun SerialDescriptor.leavesOfSealedClasses(): List<SerialDescriptor> {
-    return if (this.kind == PolymorphicKind.SEALED) {
-        elementDescriptors.filter { it.kind == SerialKind.CONTEXTUAL }.flatMap { it.elementDescriptors }
-            .flatMap { it.leavesOfSealedClasses() }
-    } else {
-        listOf(this)
-    }
-}
+import kotlinx.serialization.serializer
+import kotlin.reflect.full.starProjectedType
 
 @ExperimentalSerializationApi
 fun SerialDescriptor.possibleSerializationSubclasses(serializersModule: SerializersModule): List<SerialDescriptor> {
     return when (this.kind) {
-        PolymorphicKind.SEALED -> this.leavesOfSealedClasses()
-        PolymorphicKind.OPEN -> serializersModule.getPolymorphicDescriptors(this).sortedBy { it.serialName }
+        StructureKind.CLASS, StructureKind.OBJECT -> listOf(this)
+        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) }
+            }
+        }
         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/schema/UnionSchemaFor.kt b/src/main/kotlin/com/github/avrokotlin/avro4k/schema/UnionSchemaFor.kt
index 0388686..715ef90 100644
--- a/src/main/kotlin/com/github/avrokotlin/avro4k/schema/UnionSchemaFor.kt
+++ b/src/main/kotlin/com/github/avrokotlin/avro4k/schema/UnionSchemaFor.kt
@@ -8,15 +8,19 @@ import kotlinx.serialization.modules.SerializersModule
 import org.apache.avro.Schema
 
 @ExperimentalSerializationApi
-class UnionSchemaFor(private val descriptor: SerialDescriptor,
-                     private val namingStrategy: NamingStrategy,
-                     private val serializersModule: SerializersModule,
-                     private val resolvedSchemas: MutableMap<RecordNaming, Schema>
+class UnionSchemaFor(
+    private val descriptor: SerialDescriptor,
+    private val namingStrategy: NamingStrategy,
+    private val serializersModule: SerializersModule,
+    private val resolvedSchemas: MutableMap<RecordNaming, Schema>
 ) : SchemaFor {
-   override fun schema(): Schema {
-      val leafSerialDescriptors = descriptor.possibleSerializationSubclasses(serializersModule)
-      return Schema.createUnion(
-         leafSerialDescriptors.map { ClassSchemaFor(it,namingStrategy,serializersModule, resolvedSchemas).schema() }
-      )
-   }
+    override fun schema(): Schema {
+        val leafSerialDescriptors =
+            descriptor.possibleSerializationSubclasses(serializersModule).sortedBy { it.serialName }
+        return Schema.createUnion(
+            leafSerialDescriptors.map {
+                ClassSchemaFor(it, namingStrategy, serializersModule, resolvedSchemas).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 ed219d3..e8fb26d 100644
--- a/src/test/kotlin/com/github/avrokotlin/avro4k/io/CollectionsIoTest.kt
+++ b/src/test/kotlin/com/github/avrokotlin/avro4k/io/CollectionsIoTest.kt
@@ -2,13 +2,13 @@
 
 package com.github.avrokotlin.avro4k.io
 
-import io.kotest.matchers.shouldBe
 import io.kotest.core.spec.style.StringSpec
+import io.kotest.matchers.shouldBe
 import io.kotest.property.Arb
-import io.kotest.property.arbitrary.filter
+import io.kotest.property.arbitrary.double
 import io.kotest.property.arbitrary.int
 import io.kotest.property.arbitrary.long
-import io.kotest.property.arbitrary.positiveDoubles
+import io.kotest.property.arbitrary.numericDoubles
 import io.kotest.property.checkAll
 import kotlinx.serialization.Serializable
 import org.apache.avro.generic.GenericArray
@@ -48,12 +48,27 @@ class CollectionsIoTest : StringSpec({
 
       // there's a bug in avro with Double.POSINF
       checkAll(
-         Arb.positiveDoubles().filter { it < 1000000 },
-         Arb.positiveDoubles().filter { it < 1000000 },
-         Arb.positiveDoubles().filter { it < 1000000 }
+         Arb.double(),
+         Arb.double(),
+         Arb.double()
+      ) { a, b, c ->
+         val data = DoubleArrayTest(arrayOf(a, b, c))
+         val serializer = DoubleArrayTest.serializer()
+         val test : (GenericRecord) -> Any = {it["a"] shouldBe listOf(a,b,c)}
+         writeReadData(data, serializer, test = test)
+         writeReadBinary(data, serializer, test = test)
+      }
+   }
+
+   "read / write arrays of double in json" {
+      //Json does not support -inf/+inf and NaN
+      checkAll(
+         Arb.numericDoubles(),
+         Arb.numericDoubles(),
+         Arb.numericDoubles()
       ) { a, b, c ->
-         writeRead(DoubleArrayTest(arrayOf(a, b, c)), DoubleArrayTest.serializer()) {
-            it["a"] shouldBe listOf(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/io/StreamTests.kt b/src/test/kotlin/com/github/avrokotlin/avro4k/io/StreamTests.kt
index 28a96d0..012335c 100644
--- a/src/test/kotlin/com/github/avrokotlin/avro4k/io/StreamTests.kt
+++ b/src/test/kotlin/com/github/avrokotlin/avro4k/io/StreamTests.kt
@@ -43,16 +43,29 @@ fun <T> writeRead(t: T, expected: T, serializer: KSerializer<T>, avro: Avro = Av
 }
 
 fun <T> writeRead(t: T, serializer: KSerializer<T>, avro: Avro = Avro.default, test: (GenericRecord) -> Any) {
-   writeData(t, serializer, avro).apply {
-      val record = readData(this, serializer, avro)
+   writeReadData(t, serializer, avro, test)
+   writeReadBinary(t, serializer, avro, test)
+   writeReadJson(t, serializer, avro, test)
+}
+
+fun <T> writeReadJson(t: T, serializer: KSerializer<T>, avro: Avro = Avro.default, test: (GenericRecord) -> Any
+) {
+   writeJson(t, serializer, avro).apply {
+      val record = readJson(this, serializer, avro)
       test(record)
    }
+}
+
+fun <T> writeReadBinary(t: T, serializer: KSerializer<T>, avro: Avro = Avro.default , test: (GenericRecord) -> Any) {
    writeBinary(t, serializer, avro).apply {
       val record = readBinary(this, serializer, avro)
       test(record)
    }
-   writeJson(t, serializer, avro).apply {
-      val record = readJson(this, serializer, avro)
+}
+
+fun <T> writeReadData(t: T, serializer: KSerializer<T>, avro: Avro = Avro.default, test: (GenericRecord) -> Any) {
+   writeData(t, serializer, avro).apply {
+      val record = readData(this, serializer, avro)
       test(record)
    }
 }
diff --git a/src/test/kotlin/com/github/avrokotlin/avro4k/schema/MixedPolymorphicSchemaTest.kt b/src/test/kotlin/com/github/avrokotlin/avro4k/schema/MixedPolymorphicSchemaTest.kt
new file mode 100644
index 0000000..346f095
--- /dev/null
+++ b/src/test/kotlin/com/github/avrokotlin/avro4k/schema/MixedPolymorphicSchemaTest.kt
@@ -0,0 +1,67 @@
+package com.github.avrokotlin.avro4k.schema
+
+import com.github.avrokotlin.avro4k.Avro
+import com.github.avrokotlin.avro4k.AvroDefault
+import io.kotest.core.spec.style.StringSpec
+import io.kotest.matchers.shouldBe
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.modules.SerializersModule
+import kotlinx.serialization.modules.polymorphic
+import kotlinx.serialization.modules.subclass
+import org.apache.avro.Schema
+
+sealed interface Expr
+sealed interface UnaryExpr : Expr {
+    val value : Int
+}
+@Serializable
+sealed class BinaryExpr : Expr {
+    abstract val left: Int
+    abstract val right: Int
+}
+@Serializable
+object NullaryExpr : Expr
+@Serializable
+data class NegateExpr(override val value : Int) : UnaryExpr
+@Serializable
+data class AddExpr(override val left : Int, override val right : Int) : BinaryExpr()
+@Serializable
+data class SubstractExpr(override val left : Int, override val right : Int) : BinaryExpr()
+@Serializable
+abstract class OtherBinaryExpr : BinaryExpr()
+@Serializable
+data class MultiplicationExpr(override val left : Int, override val right : Int) : OtherBinaryExpr()
+@Serializable
+abstract class OtherUnaryExpr : UnaryExpr
+@Serializable
+data class ConstExpr(override val value : Int) : OtherUnaryExpr()
+
+@Serializable
+data class ReferencingMixedPolymorphic(
+    val notNullable: Expr
+)
+@Serializable
+data class ReferencingNullableMixedPolymorphic(
+    @AvroDefault(Avro.NULL)
+    val nullable : Expr?
+)
+class MixedPolymorphicSchemaTest : StringSpec({
+    val module = SerializersModule {
+        polymorphic(OtherUnaryExpr::class) {
+            subclass(ConstExpr::class)
+        }
+        polymorphic(OtherBinaryExpr::class) {
+            subclass(MultiplicationExpr::class)
+        }
+    }
+    "referencing a mixed sealed hierarchy"{
+        val schema = Avro(module).schema(ReferencingMixedPolymorphic.serializer())
+        val expected = Schema.Parser().parse(javaClass.getResourceAsStream("/mixed_polymorphic_referenced.json"))
+        schema shouldBe expected
+    }
+    "referencing a mixed nullable sealed hierarchy"{
+        val schema = Avro(module).schema(ReferencingNullableMixedPolymorphic.serializer())
+        val expected = Schema.Parser().parse(javaClass.getResourceAsStream("/mixed_polymorphic_nullable_referenced.json"))
+        schema shouldBe expected
+    }
+})
\ No newline at end of file
diff --git a/src/test/kotlin/com/github/avrokotlin/avro4k/schema/PolymorphicClassSchemaTest.kt b/src/test/kotlin/com/github/avrokotlin/avro4k/schema/PolymorphicClassSchemaTest.kt
index 4cf2a39..c291c4f 100644
--- a/src/test/kotlin/com/github/avrokotlin/avro4k/schema/PolymorphicClassSchemaTest.kt
+++ b/src/test/kotlin/com/github/avrokotlin/avro4k/schema/PolymorphicClassSchemaTest.kt
@@ -9,7 +9,6 @@ import kotlinx.serialization.modules.SerializersModule
 import kotlinx.serialization.modules.polymorphic
 import kotlinx.serialization.modules.subclass
 import org.apache.avro.Schema
-
 @Serializable
 abstract class UnsealedPolymorphicRoot
 
@@ -17,7 +16,9 @@ abstract class UnsealedPolymorphicRoot
 data class UnsealedChildOne(val one: String) : UnsealedPolymorphicRoot()
 
 @Serializable
-data class UnsealedChildTwo(val two: String) : UnsealedPolymorphicRoot()
+sealed class SealedChildTwo : UnsealedPolymorphicRoot()
+@Serializable
+data class UnsealedChildTwo(val two: String) : SealedChildTwo()
 
 @Serializable
 data class ReferencingPolymorphicRoot(
@@ -30,7 +31,7 @@ class PolymorphicClassSchemaTest : StringSpec({
       val module = SerializersModule {
          polymorphic(UnsealedPolymorphicRoot::class) {
             subclass(UnsealedChildOne::class)
-            subclass(UnsealedChildTwo::class)
+            subclass(SealedChildTwo::class)
          }
       }
       val schema = Avro(serializersModule = module).schema(UnsealedPolymorphicRoot.serializer())
diff --git a/src/test/kotlin/com/github/avrokotlin/avro4k/schema/SealedInterfaceSchemaTest.kt b/src/test/kotlin/com/github/avrokotlin/avro4k/schema/SealedInterfaceSchemaTest.kt
new file mode 100644
index 0000000..f145906
--- /dev/null
+++ b/src/test/kotlin/com/github/avrokotlin/avro4k/schema/SealedInterfaceSchemaTest.kt
@@ -0,0 +1,50 @@
+package com.github.avrokotlin.avro4k.schema
+
+import com.github.avrokotlin.avro4k.Avro
+import com.github.avrokotlin.avro4k.AvroDefault
+import io.kotest.core.spec.style.StringSpec
+import io.kotest.matchers.shouldBe
+import kotlinx.serialization.Serializable
+import org.apache.avro.Schema
+
+sealed interface Calculable
+sealed interface UnaryCalculable : Calculable {
+   val value : Int
+}
+sealed interface BinaryCalculable : Calculable {
+   val left: Int
+   val right: Int
+}
+@Serializable
+object NullaryCalculable : Calculable
+@Serializable
+data class NegateCalculable(override val value : Int) : UnaryCalculable
+@Serializable
+data class AddCalculable(override val left : Int, override val right : Int) : BinaryCalculable
+@Serializable
+data class SubstractCalculable(override val left : Int, override val right : Int) : BinaryCalculable
+
+
+@Serializable
+data class ReferencingSealedInterface(
+   val notNullable: Calculable
+)
+@Serializable
+data class ReferencingNullableSealedInterface(
+   @AvroDefault(Avro.NULL)
+   val nullable : Calculable?
+)
+
+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"))
+      schema shouldBe expected
+   }
+   "referencing a nullable sealed hierarchy"{
+      val schema = Avro.default.schema(ReferencingNullableSealedInterface.serializer())
+      val expected = Schema.Parser().parse(javaClass.getResourceAsStream("/sealed_interface_nullable_referenced.json"))
+      schema shouldBe expected
+   }
+})
\ No newline at end of file
diff --git a/src/test/resources/mixed_polymorphic_nullable_referenced.json b/src/test/resources/mixed_polymorphic_nullable_referenced.json
new file mode 100644
index 0000000..655021a
--- /dev/null
+++ b/src/test/resources/mixed_polymorphic_nullable_referenced.json
@@ -0,0 +1,81 @@
+{
+   "type":"record",
+   "name":"ReferencingNullableMixedPolymorphic",
+   "namespace":"com.github.avrokotlin.avro4k.schema",
+   "fields": [
+      {
+         "name":"nullable",
+         "type":[
+            "null",
+            {
+               "type":"record",
+               "name":"AddExpr",
+               "fields":[
+                  {
+                     "name":"left",
+                     "type":"int"
+                  },
+                  {
+                     "name":"right",
+                     "type":"int"
+                  }
+               ]
+            },
+            {
+               "type":"record",
+               "name":"ConstExpr",
+               "fields":[
+                  {
+                     "name":"value",
+                     "type":"int"
+                  }
+               ]
+            },
+            {
+               "type":"record",
+               "name":"MultiplicationExpr",
+               "fields":[
+                  {
+                     "name":"left",
+                     "type":"int"
+                  },
+                  {
+                     "name":"right",
+                     "type":"int"
+                  }
+               ]
+            },
+            {
+               "type":"record",
+               "name":"NegateExpr",
+               "fields":[
+                  {
+                     "name":"value",
+                     "type":"int"
+                  }
+               ]
+            },
+            {
+               "type":"record",
+               "name":"NullaryExpr",
+               "fields":[]
+            },
+            {
+               "type":"record",
+               "name":"SubstractExpr",
+               "fields":[
+                  {
+                     "name":"left",
+                     "type":"int"
+                  },
+                  {
+                     "name":"right",
+                     "type":"int"
+                  }
+               ]
+            }
+         ],
+         "default" : null
+      }
+   ]
+}
diff --git a/src/test/resources/mixed_polymorphic_referenced.json b/src/test/resources/mixed_polymorphic_referenced.json
new file mode 100644
index 0000000..c80e038
--- /dev/null
+++ b/src/test/resources/mixed_polymorphic_referenced.json
@@ -0,0 +1,79 @@
+{
+   "type":"record",
+   "name":"ReferencingMixedPolymorphic",
+   "namespace":"com.github.avrokotlin.avro4k.schema",
+   "fields": [
+      {
+         "name":"notNullable",
+         "type":[
+            {
+               "type":"record",
+               "name":"AddExpr",
+               "fields":[
+                  {
+                     "name":"left",
+                     "type":"int"
+                  },
+                  {
+                     "name":"right",
+                     "type":"int"
+                  }
+               ]
+            },
+            {
+               "type":"record",
+               "name":"ConstExpr",
+               "fields":[
+                  {
+                     "name":"value",
+                     "type":"int"
+                  }
+               ]
+            },
+            {
+               "type":"record",
+               "name":"MultiplicationExpr",
+               "fields":[
+                  {
+                     "name":"left",
+                     "type":"int"
+                  },
+                  {
+                     "name":"right",
+                     "type":"int"
+                  }
+               ]
+            },
+            {
+               "type":"record",
+               "name":"NegateExpr",
+               "fields":[
+                  {
+                     "name":"value",
+                     "type":"int"
+                  }
+               ]
+            },
+            {
+               "type":"record",
+               "name":"NullaryExpr",
+               "fields":[]
+            },
+            {
+               "type":"record",
+               "name":"SubstractExpr",
+               "fields":[
+                  {
+                     "name":"left",
+                     "type":"int"
+                  },
+                  {
+                     "name":"right",
+                     "type":"int"
+                  }
+               ]
+            }
+         ]
+      }
+   ]
+}
diff --git a/src/test/resources/sealed.json b/src/test/resources/sealed.json
index 1197b14..bcd900a 100644
--- a/src/test/resources/sealed.json
+++ b/src/test/resources/sealed.json
@@ -1,21 +1,4 @@
 [
-   {
-      "type":"record",
-      "name":"Nullary",
-      "namespace":"com.github.avrokotlin.avro4k.schema.Operation",
-      "fields":[]
-   },
-   {
-      "type":"record",
-      "name":"Negate",
-      "namespace":"com.github.avrokotlin.avro4k.schema.Operation.Unary",
-      "fields":[
-         {
-            "name":"value",
-            "type":"int"
-         }
-      ]
-   },
    {
       "type":"record",
       "name":"Add",
@@ -45,5 +28,23 @@
             "type":"int"
          }
       ]
+   },
+   {
+      "type":"record",
+      "name":"Nullary",
+      "namespace":"com.github.avrokotlin.avro4k.schema.Operation",
+      "fields":[]
+   },
+   {
+      "type":"record",
+      "name":"Negate",
+      "namespace":"com.github.avrokotlin.avro4k.schema.Operation.Unary",
+      "fields":[
+         {
+            "name":"value",
+            "type":"int"
+         }
+      ]
    }
+
 ]
diff --git a/src/test/resources/sealed_interface_nullable_referenced.json b/src/test/resources/sealed_interface_nullable_referenced.json
new file mode 100644
index 0000000..4d282e9
--- /dev/null
+++ b/src/test/resources/sealed_interface_nullable_referenced.json
@@ -0,0 +1,57 @@
+{
+   "type":"record",
+   "name":"ReferencingNullableSealedInterface",
+   "namespace":"com.github.avrokotlin.avro4k.schema",
+   "fields":[
+      {
+         "name":"nullable",
+         "type":[
+            "null",
+            {
+               "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"
+                  }
+               ]
+            }
+         ],
+         "default":null
+      }
+   ]
+}
diff --git a/src/test/resources/sealed_interface_referenced.json b/src/test/resources/sealed_interface_referenced.json
new file mode 100644
index 0000000..fe60b98
--- /dev/null
+++ b/src/test/resources/sealed_interface_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_nullable_referenced.json b/src/test/resources/sealed_nullable_referenced.json
index 900086f..0db5860 100644
--- a/src/test/resources/sealed_nullable_referenced.json
+++ b/src/test/resources/sealed_nullable_referenced.json
@@ -7,23 +7,6 @@
          "name":"nullable",
          "type":[
             "null",
-            {
-               "type":"record",
-               "name":"Nullary",
-               "namespace":"com.github.avrokotlin.avro4k.schema.Operation",
-               "fields":[]
-            },
-            {
-               "type":"record",
-               "name":"Negate",
-               "namespace":"com.github.avrokotlin.avro4k.schema.Operation.Unary",
-               "fields":[
-                  {
-                     "name":"value",
-                     "type":"int"
-                  }
-               ]
-            },
             {
                "type":"record",
                "name":"Add",
@@ -53,9 +36,26 @@
                      "type":"int"
                   }
                ]
+            },
+            {
+               "type":"record",
+               "name":"Nullary",
+               "namespace":"com.github.avrokotlin.avro4k.schema.Operation",
+               "fields":[]
+            },
+            {
+               "type":"record",
+               "name":"Negate",
+               "namespace":"com.github.avrokotlin.avro4k.schema.Operation.Unary",
+               "fields":[
+                  {
+                     "name":"value",
+                     "type":"int"
+                  }
+               ]
             }
          ],
-         "default":null
+         "default" : null
       }
    ]
 }
diff --git a/src/test/resources/sealed_referenced.json b/src/test/resources/sealed_referenced.json
index 500a681..aca6855 100644
--- a/src/test/resources/sealed_referenced.json
+++ b/src/test/resources/sealed_referenced.json
@@ -6,23 +6,6 @@
       {
          "name":"notNullable",
          "type":[
-            {
-               "type":"record",
-               "name":"Nullary",
-               "namespace":"com.github.avrokotlin.avro4k.schema.Operation",
-               "fields":[]
-            },
-            {
-               "type":"record",
-               "name":"Negate",
-               "namespace":"com.github.avrokotlin.avro4k.schema.Operation.Unary",
-               "fields":[
-                  {
-                     "name":"value",
-                     "type":"int"
-                  }
-               ]
-            },
             {
                "type":"record",
                "name":"Add",
@@ -52,6 +35,23 @@
                      "type":"int"
                   }
                ]
+            },
+            {
+               "type":"record",
+               "name":"Nullary",
+               "namespace":"com.github.avrokotlin.avro4k.schema.Operation",
+               "fields":[]
+            },
+            {
+               "type":"record",
+               "name":"Negate",
+               "namespace":"com.github.avrokotlin.avro4k.schema.Operation.Unary",
+               "fields":[
+                  {
+                     "name":"value",
+                     "type":"int"
+                  }
+               ]
             }
          ]
       }