When I use the open-api-generator
all of the fields are nullable, all classes look like this:
data class PagedResultDtoOfStoredDeviceFilterWithoutTermDto (
@Json(name = "currentPage")
val currentPage: kotlin.Int? = null,
@Json(name = "totalPages")
val totalPages: kotlin.Int? = null,
@Json(name = "pageSize")
val pageSize: kotlin.Int? = null,
@Json(name = "totalCount")
val totalCount: kotlin.Int? = null,
@Json(name = "items")
val items: kotlin.collections.List<StoredDeviceFilterWithoutTermDto>? = null
)
Despite many fields are NOT nullable server-side, as you can see in the swagger.json This makes the generator unusable to me, do somebody know why this happens?
This is my generator config:
java -jar openapi-generator-cli.jar generate ^
-g kotlin ^
--library jvm-retrofit2 ^
-i "https://app-hsm-clse-stage.azurewebsites.net/swagger/standardV1/swagger.json" ^
-p groupId=com.mycompany ^
-p artifactId=mycompany-myproduct-client-kotlin ^
-p artifactVersion=1.0.0 ^
-p basePackage=com.mycompany.myproduct.networking ^
-p packageName=com.mycompany.myproduct.networking ^
-p configPackage=com.mycompany.myproduct.networking.config ^
-p apiPackage=com.mycompany.myproduct.networking.api ^
-p modelPackage=com.mycompany.myproduct.networking.model ^
-p sourceFolder=src/main/java ^
-p dateLibrary=java8 ^
-p java8=true ^
-p useRxJava3=true ^
pause
You could potentially accomplish this using templates and extensions.
Create a vendor extension called x-is-nullable
and add it to your object schema. I'm using your AbsoluteDateIntervalDto
object as an example:
AbsoluteDateIntervalDto:
type: object
properties:
startDate:
type: string
format: date-time
x-is-nullable: "false"
endDate:
type: string
format: date-time
x-is-nullable: "false"
startIncluded:
type: boolean
default: true
endIncluded:
type: boolean
additionalProperties: false
Note that I added a default value to startIncluded
to demonstrate how this works with default values as well.
now, change the template for data_class_opt_var.mustache
. Replace the last line so that it looks like this:
{{^vendorExtensions.x-is-nullable}}{{#multiplatform}}@SerialName(value = "{{{vendorExtensions.x-base-name-literal}}}") {{/multiplatform}}{{#isInherited}}override {{/isInherited}}{{>modelMutable}} {{{name}}}: {{#isArray}}{{#isList}}{{#uniqueItems}}kotlin.collections.{{#modelMutable}}Mutable{{/modelMutable}}Set{{/uniqueItems}}{{^uniqueItems}}kotlin.collections.{{#modelMutable}}Mutable{{/modelMutable}}List{{/uniqueItems}}{{/isList}}{{^isList}}kotlin.Array{{/isList}}<{{^items.isEnum}}{{^items.isPrimitiveType}}{{^items.isModel}}{{#kotlinx_serialization}}@Contextual {{/kotlinx_serialization}}{{/items.isModel}}{{/items.isPrimitiveType}}{{{items.dataType}}}{{/items.isEnum}}{{#items.isEnum}}{{classname}}.{{{nameInCamelCase}}}{{/items.isEnum}}>{{/isArray}}{{^isEnum}}{{^isArray}}{{{dataType}}}{{/isArray}}{{/isEnum}}{{#isEnum}}{{^isArray}}{{classname}}.{{{nameInCamelCase}}}{{/isArray}}{{/isEnum}}? = {{^defaultValue}}null{{/defaultValue}}{{#defaultValue}}{{^isNumber}}{{{defaultValue}}}{{/isNumber}}{{#isNumber}}{{^multiplatform}}{{{dataType}}}("{{{defaultValue}}}"){{/multiplatform}}{{#multiplatform}}({{{defaultValue}}}).toDouble(){{/multiplatform}}{{/isNumber}}{{/defaultValue}}{{/vendorExtensions.x-is-nullable}}{{#vendorExtensions.x-is-nullable}}{{#multiplatform}}@SerialName(value = "{{{vendorExtensions.x-base-name-literal}}}") {{/multiplatform}}{{#isInherited}}override {{/isInherited}}{{>modelMutable}} {{{name}}}: {{#isArray}}{{#isList}}{{#uniqueItems}}kotlin.collections.{{#modelMutable}}Mutable{{/modelMutable}}Set{{/uniqueItems}}{{^uniqueItems}}kotlin.collections.{{#modelMutable}}Mutable{{/modelMutable}}List{{/uniqueItems}}{{/isList}}{{^isList}}kotlin.Array{{/isList}}<{{^items.isEnum}}{{^items.isPrimitiveType}}{{^items.isModel}}{{#kotlinx_serialization}}@Contextual {{/kotlinx_serialization}}{{/items.isModel}}{{/items.isPrimitiveType}}{{{items.dataType}}}{{/items.isEnum}}{{#items.isEnum}}{{classname}}.{{{nameInCamelCase}}}{{/items.isEnum}}>{{/isArray}}{{^isEnum}}{{^isArray}}{{{dataType}}}{{/isArray}}{{/isEnum}}{{#isEnum}}{{^isArray}}{{classname}}.{{{nameInCamelCase}}}{{/isArray}}{{/isEnum}}{{#defaultValue}} = {{^isNumber}}{{{defaultValue}}}{{/isNumber}}{{#isNumber}}{{^multiplatform}}{{{dataType}}}("{{{defaultValue}}}"){{/multiplatform}}{{#multiplatform}}({{{defaultValue}}}).toDouble(){{/multiplatform}}{{/isNumber}}{{/defaultValue}}{{/vendorExtensions.x-is-nullable}}
Note: this is a very long line, however it must all be on one line for it to work correctly.
Here's how it works. The open-api generator
will put all vendor extensions into an array called vendorExtensions
that is accessible to the mustache templating engine. The line {{^vendorExtensions.x-is-nullable}}
checks to see if the x-is-nullable
extension exists in the array. the carrot(^
) is basically equals to if (x-is-nullable.doesNotExist())
. The value false
assigned in the preceding yaml is arbitrary. You can have any value you want there, but a value must exist.
Now the template will execute all of the code between the {{^vendorExtensions.x-is-nullable}}
and the closing {{/vendorExtensions.x-is-nullable}}
if the vendor extension doesn't exist, and it will execute all the code between {{#vendorExtensions.x-is-nullable}}{{/vendorExtensions.x-is-nullable}}
if it does. This allows two different generations to occur in the same class.
Your new resulting data class from the above template change, using the above schema, will now look like this
data class AbsoluteDateIntervalDto (
@Json(name = "startDate")
val startDate: java.time.OffsetDateTime,
@Json(name = "endDate")
val endDate: java.time.OffsetDateTime,
@Json(name = "startIncluded")
val startIncluded: kotlin.Boolean = true,
@Json(name = "endIncluded")
val endIncluded: kotlin.Boolean? = null
)