rest-guide icon indicating copy to clipboard operation
rest-guide copied to clipboard

Recommended use of discriminator, oneOf, inheritance

Open pvdbosch opened this issue 1 year ago • 2 comments

We're regularly encountering errors when oneOf or discriminators are being used.

  • OpenAPI document authors sometimes mistakenly assume that the oneOf subschema types are part of the JSON message; and that it can easily be retrieved as a data element by applications interacting with the API.
  • oneOf with object-type subschemas and discriminator property should work
    • this generates inheritance with type info annotations to deserialization can be done to the correct generated child class
    • we may recommend always use "mapping", and against serializing the schema names (they're also not lowerCamelCase so invalid wrt [cod-design]
    • for "any-type" lists, like "embedded", this could become difficult though
  • oneOf with object-type subschemas without a discriminator is not deserializable
    • it's missing type info annotations needed to determine child class, but to be tested if inherited classes actually get generated in this case
  • oneOf with simple, non-object types, leads to code generation problems
    • recommend to either replace by single combined schema w/o oneOf, or use explicit separate properties
  • oneOf with subschemas that only define "required" properties can be a good alternative, like used for BelgianRemittanceInformation
    • this generates a flat class structure. The allowed combinations of properties isn't visible in generated classes, but can still be validated if validating the exchanged messages against the schema in the OpenAPI document

Note that to generate a classes in an inheritance structure, we're using an allOf to reference the parent from in the child schema, and configure REF_AS_PARENT_IN_ALLOF=true. This is also needed, even with discriminator. The child schema shouldn't redefine properties already present in the parent. In theory, this can be done to specify additional constraints on the property, but this will generate a duplicate attribute in parent and child class.

pvdbosch avatar Apr 17 '24 10:04 pvdbosch

Confirmed that oneOf with simple types gives bad code generation results, for instance:

  BelgianRemittanceInformationOneOfTwoStrings:
      oneOf:
        - description: Unstructured Remittance Information as supported by Belgian banks
          type: string
          maxLength: 140
        - description: Structured Remittance Information in the Belgian OGM-VCS format. Often formatted for display as +++ 3 digits / 4 digits / 5 digits / +++.
          type: string
          pattern: "^\\d{12}$"
          example: "010806817183"

generates:

public interface BelgianRemittanceInformationOneOfTwoStrings {
}

without any subclasses of this empty interface. Tested using openapi-generator 7.5.0, same behavior when using referenced subschemas instead of inline ones.

pvdbosch avatar Apr 17 '24 15:04 pvdbosch

Tested oneOf with object-type subschemas without discriminator in Java. It doesn't seem to work as desired:

  • inheritance is generated, but the base interface doesn't have any common properties
  • subclasses are missing @JsonTypeInfo, to jackson can't determine subtype to use when deserializing
    BelgianRemittanceInformationOneOfTwoObjects:
      type: object
      properties:
        variant:
          type: string
          enum:
            - variantA
            - variantB
      oneOf:
        - $ref: "#/components/schemas/BelgianRemittanceInformationUnstructuredObject"
        - $ref: "#/components/schemas/BelgianRemittanceInformationStructuredObject"
    BelgianRemittanceInformationStructuredObject:
      type: object
      properties:
        structured:
          $ref: "#/components/schemas/BelgianRemittanceInformationStructured"
    BelgianRemittanceInformationUnstructuredObject:
      type: object
      properties:
        unstructured:
          $ref: "#/components/schemas/BelgianRemittanceInformationUnstructured"

warning: 'oneOf' is intended to include only the additional optional OAS extension discriminator object. For more details, see https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.9.2.1.3 and the OAS section on 'Composition and Inheritance'.

generated Java code:

  • empty BelgianRemittanceInformationOneOfTwoObjects interface
  • BelgianRemittanceInformationStructuredObject and BelgianRemittanceInformationUnstructuredObject subclasses with all properties, including the "variant" property

pvdbosch avatar Apr 17 '24 15:04 pvdbosch