json-kotlin-schema-codegen icon indicating copy to clipboard operation
json-kotlin-schema-codegen copied to clipboard

Kotlin Multiplatform support

Open mattinger opened this issue 2 years ago • 5 comments

I've noticed that fields like this end up using BigDecimal, which means they are not pure kotlin, and can't be used in multiplatform projects.

"type": "number",
"format": "decimal"

It also makes me jump through some hoops with my serializer configuration to be able to parse the BigDecimal properly within the jvm environment. It can be done, but i'd rather have a KMP friendly way of generating the models.

    val JsonSerializer by lazy {
        Json {
            serializersModule = SerializersModule {
                contextual(BigDecimal::class, BigDecimalSerializer)
            }
        }
    }

Either that, or i have to alter my configuration for the generator:

{
  "annotations": {
    "classes": {
      "kotlinx.serialization.Serializable": ""
    },
    "fields": {
      "kotlinx.serialization.Serializable": "{{#isDecimal}}with = BigDecimalSerializer::class{{/isDecimal}}"
    }
  }
}

mattinger avatar Oct 20 '23 17:10 mattinger

Hi - I have to admit that I have only limited knowledge of the Kotlin multiplatform environment; all my experience is with JVM versions of the language. I would like to make my software usable on as wide a range of projects as possible, and I'm keen to hear suggestions in this regard.

BigDecimal was chosen for the representation of JSON floating-point numbers on the JVM because JSON numbers are decimal, and JVM Double and Float can not hold decimal numbers without loss of accuracy.

What is the Kotlin multiplatform solution to this problem? Is there a standard way of handling floating-point numbers? I would be happy to add a configuration option to use an alternative class instead of BigDecimal, but I'm looking for guidance on what class would be applicable in that environment.

pwall567 avatar Oct 22 '23 13:10 pwall567

@pwall567 What we've been doing as a workaround is to have the Gradle build file process all the generated sources and have all references to the package java.math.BigDecimal replaced with com.ionspin.kotlin.bignum.decimal.BigDecimal. Also we wrote some replacement magic to annotate any BigDecimal vals with @kotlinx.serialization.Contextual.

Of course, this also required us to add kotlin-multiplatform-bignum as a dependency to our project. This project provides Kotlin multi-platform alternatives for Java-specific types such as BigDecimal. This resulted in generated code that successfully compiled in a multi-platform project to multiple targets (Kotlin/JVM, Kotlin/JS and Kotlin/Native).

Unfortunately, we did run into some kind of run-time issue with kotlinx-serialization still not being able to (de)serialize the BigDecimal type provided by the bignum project, even though that project supposedly came with the necessary serializers. As a quick fix, we temporarily just had the Gradle build file replace the BigDecimal type in the generated code with Double. Yes, it potentially loses precision, but for the specific use case that we were working on, it sufficed for the time being, although we would like to get it working with bignum's multi-platform types.

It would be nice if we could at least have the option to configure json-kotlin-schema-codegen (and the build plugins that depend on it) to use those bignum types instead of Java-specific types, or perhaps even the freedom to specify other types.

volkert-fastned avatar Dec 01 '23 14:12 volkert-fastned

One other thing that we had to do to ensure multiplatform compatibility was to tell JSONSchemaCodegen to use kotlinx.datetime types instead of Java JSR-310 types:

    configure<JSONSchemaCodegen> {

        // (Other possible existing project-specific configuration stuff here)

        classMappings {
            // Ensure that we use Multiplatform kotlinx-datetime types instead of Java-specific JSR-310 types
            byFormat("kotlinx.datetime.Instant", "date-time")
        }
    }

I would recommend that json-kotlin-schema-codegen prefer standard Kotlin-specific types over Java/JVM types as much as possible. (In the case of BigDecimal, it's a bit less trivial, since Kotlin doesn't have equivalent types for those in its standard library.)

volkert-fastned avatar Dec 01 '23 14:12 volkert-fastned

Hi @volkert-fastned , thanks for this information. I have to admit I had never heard of the kotlinx.datetime classes – my use of Kotlin in my day job is all JVM-based, and in that world, the java.time classes prevail. I'm reluctant to change the default, and as you've already found, it's not difficult to specify the classes to be used for the format keywords (can I recommend the use of a config file rather than the setting in the Gradle file in your example).

But BigDecimal is a different issue, as you have noted. I think it should be possible to allow a configuration option to specify the class to be used for decimal values – I'll look into that and get back to you shortly.

Thanks again for the feedback, Peter

pwall567 avatar Dec 04 '23 13:12 pwall567

@pwall567 It may be reassuring to know that in the case of the Kotlin/JVM platform, kotlinx.datetime uses java.time (JSR-310) underneath, and has convenient conversion functions such as Instant.toJavaInstant(), LocalDate.toJavaLocalDate(), etc.

It's just a multi-platform abstraction layer.

So even for projects relying on JSR-310, it shouldn't be much of a problem if kotlinx.datetime were used by default.

volkert-fastned avatar Dec 15 '23 11:12 volkert-fastned