swagger-api / swagger-codegen

swagger-codegen contains a template-driven engine to generate documentation, API clients and server stubs in different languages by parsing your OpenAPI / Swagger definition.
http://swagger.io
Apache License 2.0
16.98k stars 6.03k forks source link

[Kotlin] Enum deserialization not working #7811

Open lopesmcc opened 6 years ago

lopesmcc commented 6 years ago
Description

When deserializing a response, if enums are used, and the enum value doesn't match with the enum name, an exception is thrown by moshi stating that the enum doesn't match expected values.

E.g.:

status: {
  type: "string",
  description: "Order Status",
  enum: [
    "Placed",
    "Approved",
    "Delivered"
  ]
}

When deserializing, it fails with: expected one of [placed, approved, delivered] but was Placed at path $.status

This happens because moshi's EnumJsonAdapter doesn't use the enum's getValue and instead either uses a Json annotation or name().

Swagger-codegen version

2.3.1

Command line used for generation

swagger-codegen generate -l kotlin -i swagger.yaml -o tmp

Steps to reproduce

Change on Petstore swagger spec any enum from lowercase to capitalized.

Suggest a fix/enhancement

A solution would be to add the Json annotation to each enum value. Another one to override the name method to return the value instead. One more is to provide a EnumJsonAdapter to moshi that can correctly decode the generated enums.

florianhof commented 6 years ago

An alternative to the proposed @Json(name = "…") would be an @JsonValue on the only field. It would be consistent with the output of Enums for Java. The result would looks as simple as that:

import com.fasterxml.jackson.annotation.JsonValue
enum class Status(@JsonValue val value: kotlin.String) {
    AVAILABLE("available"),
    PENDING("pending"),
    SOLD("sold");
}
lopesmcc commented 6 years ago

@florianhof does Moshi work with Jackson annotations?

florianhof commented 6 years ago

Oh I just improve my knowledge and realize we are probably not in the same context...

For our spring-boot web-service, manually editing the generated enum with @JsonValue solved the problem. I tried this to surround the problem. Then I switch to generate java/jersey code, which obviously also works. I doubt that Moshi's annotation would be taken into account.

For Kotlin with Moshi library (an advantage mainly for Android, but slower), then Jackson's annotations are certainly of no use.

So looks like two (or more) templates are required for kotlin. :-( But there are already two templates: kotlin-client and kotlin-server. Only kotlin-client was annotated with Moshi's Json(name="...").

A light alternative for at least kotlin-server (and should not hurt kotlin-client) would be to define toString to return the value:

override fun toString() = value

Then Jackson can be configured with READ_ENUMS_USING_TO_STRING and WRITE_ENUMS_USING_TO_STRING (with spring, something like _spring.jackson.serialization.READ_ENUMS_USING_TOSTRING: true in an application.yml). This solution is not invasive and flexible. I can only recommand to add this toString. It is in the sense described in the [javadoc](https://docs.oracle.com/javase/7/docs/api/java/lang/Enum.html#name()). By the way, Swagger calls toString to lists the possible values of an enum, so that's nicely coherent. :-)

The pure Jackson solution, reserved for kotlin-server if Jackson is available, would be to annotate either value or toString with @JsonValue.

jimschubert commented 6 years ago

@florianhof having a toString override doesn't resolve templating issues when users want multiple serialization frameworks. The generator would have to support serialization frameworks as an option, which it currently doesn't.