oneOf, anyOf, allOf

Here I talked about how to set up generation in the application.
In this article I will show several examples of a generated year using openapi composition and polymorphism. Here You can read a small section of the documentation.

1. Repository

An example of the specification and generated code can be found Here. In addition, links to specific generated classes and lines in the specification will be left throughout the text.

2. discriminator

Here You can read what a discriminator is in the openapi concept. In short, a discriminator is exactly the thing that allows you to control code generation and get the desired result when using polymorphism in the specification.

Next, we will show examples of generated code on very simple models, without and with discriminators.

3.1 OneOf without discriminator

What does it look like in specifications:

    OneOfObjectWithoutDiscriminator:
      oneOf:
        - $ref: '#/components/schemas/OneOfObjectWithoutDiscriminatorFirstProperty'
        - $ref: '#/components/schemas/OneOfObjectWithoutDiscriminatorSecondProperty'
    OneOfObjectWithoutDiscriminatorFirstProperty:
      type: object
      required:
        - someProperty
      properties:
        someProperty:
          type: string
    OneOfObjectWithoutDiscriminatorSecondProperty:
      type: object
      required:
        - anotherProperty
      properties:
        anotherProperty:
          type: string

We create an object in which we should have two string fields. What we get after generation: OneOfObjectWithoutDiscriminator, OneOfObjectWithoutDiscriminatorFirstProperty, OneOfObjectWithoutDiscriminatorSecondProperty

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public interface OneOfObjectWithoutDiscriminator {
}
/**
 * OneOfObjectWithoutDiscriminatorFirstProperty
 */
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class OneOfObjectWithoutDiscriminatorFirstProperty implements OneOfObjectWithoutDiscriminator {

  private String someProperty;

...
/**
 * OneOfObjectWithoutDiscriminatorSecondProperty
 */
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class OneOfObjectWithoutDiscriminatorSecondProperty implements OneOfObjectWithoutDiscriminator {

  private String anotherProperty;

...

When the application receives a request, such a design will break, because we will not be able to determine which implementation the incoming json is.

3.2 OneOf with discriminator

What does it look like in specifications:

OneOfObjectWithDiscriminator:
      oneOf:
        - $ref: '#/components/schemas/OneOfObjectWithDiscriminatorFirstProperty'
        - $ref: '#/components/schemas/OneOfObjectWithDiscriminatorSecondProperty'
      discriminator:
        propertyName: propertyType
        mapping:
          first: '#OneOfObjectWithDiscriminatorFirstProperty'
          second: '#OneOfObjectWithDiscriminatorSecondProperty'
    OneOfObjectWithDiscriminatorFirstProperty:
      type: object
      required:
        - propertyType
        - someProperty
      properties:
        propertyType:
          type: string
        someProperty:
          type: string
    OneOfObjectWithDiscriminatorSecondProperty:
      type: object
      required:
        - propertyType
        - anotherProperty
      properties:
        propertyType:
          type: string
        anotherProperty:
          type: string

We see that a mandatory field has been added here, the value of which will be used by the discriminator to determine the class to use when receiving a request.

What we get after generation: OneOfObjectWithDiscriminator, OneOfObjectWithDiscriminatorFirstProperty, OneOfObjectWithDiscriminatorSecondProperty

@JsonIgnoreProperties(
  value = "propertyType", // ignore manually set propertyType, it will be automatically generated by Jackson during serialization
  allowSetters = true // allows the propertyType to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "propertyType", visible = true)
@JsonSubTypes({
  @JsonSubTypes.Type(value = OneOfObjectWithDiscriminatorFirstProperty.class, name = "first"),
  @JsonSubTypes.Type(value = OneOfObjectWithDiscriminatorSecondProperty.class, name = "second"),
  @JsonSubTypes.Type(value = OneOfObjectWithDiscriminatorFirstProperty.class, name = "OneOfObjectWithDiscriminatorFirstProperty"),
  @JsonSubTypes.Type(value = OneOfObjectWithDiscriminatorSecondProperty.class, name = "OneOfObjectWithDiscriminatorSecondProperty")
})

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public interface OneOfObjectWithDiscriminator {
    public String getPropertyType();
}
/**
 * OneOfObjectWithDiscriminatorFirstProperty
 */
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class OneOfObjectWithDiscriminatorFirstProperty implements OneOfObjectWithDiscriminator {

  private String propertyType;

  private String someProperty;

...
/**
 * OneOfObjectWithDiscriminatorSecondProperty
 */
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class OneOfObjectWithDiscriminatorSecondProperty implements OneOfObjectWithDiscriminator {

  private String propertyType;

  private String anotherProperty;

...

And this is where the discriminator binding comes into play, which turns into JsonSubTypes annotations. We can already safely use such generation in our application.

4.1 AllOf without discriminator

What does it look like in specifications:

AllOfObjectWithoutDiscriminator:
      allOf:
        - $ref: '#/components/schemas/AllOfObjectWithoutDiscriminatorFirstProperty'
        - $ref: '#/components/schemas/AllOfObjectWithoutDiscriminatorSecondProperty'
    AllOfObjectWithoutDiscriminatorFirstProperty:
      type: object
      required:
        - someProperty
      properties:
        someProperty:
          type: string
    AllOfObjectWithoutDiscriminatorSecondProperty:
      type: object
      required:
        - anotherProperty
      properties:
        anotherProperty:
          type: string

Generation result: AllOfObjectWithoutDiscriminator, AllOfObjectWithoutDiscriminatorFirstProperty, AllOfObjectWithoutDiscriminatorSecondProperty

/**
 * AllOfObjectWithoutDiscriminator
 */
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AllOfObjectWithoutDiscriminator {

  private String someProperty;

  private String anotherProperty;

...
/**
 * AllOfObjectWithoutDiscriminatorFirstProperty
 */
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AllOfObjectWithoutDiscriminatorFirstProperty {

  private String someProperty;

...
/**
 * AllOfObjectWithoutDiscriminatorSecondProperty
 */
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AllOfObjectWithoutDiscriminatorSecondProperty {

  private String anotherProperty;

...

We see that AllOfObjectWithoutDiscriminator is no longer an interface, but a class, and contains both fields from the specified allOf components.

4.2 AllOf with discriminator

What does it look like in specifications:

AllOfObjectWithDiscriminator:
      allOf:
        - $ref: '#/components/schemas/AllOfObjectWithDiscriminatorFirstProperty'
        - $ref: '#/components/schemas/AllOfObjectWithDiscriminatorSecondProperty'
      discriminator:
        propertyName: propertyType
        mapping:
          first: '#AllOfObjectWithDiscriminatorFirstProperty'
          second: '#AllOfObjectWithDiscriminatorSecondProperty'
    AllOfObjectWithDiscriminatorFirstProperty:
      type: object
      required:
        - propertyType
        - someProperty
      properties:
        propertyType:
          type: string
        someProperty:
          type: string
    AllOfObjectWithDiscriminatorSecondProperty:
      type: object
      required:
        - propertyType
        - anotherProperty
      properties:
        propertyType:
          type: string
        anotherProperty:
          type: string

Generation result: AllOfObjectWithDiscriminator, AllOfObjectWithDiscriminatorFirstProperty, AllOfObjectWithDiscriminatorSecondProperty

/**
 * AllOfObjectWithDiscriminator
 */
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor


@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AllOfObjectWithDiscriminator {

  private String propertyType;

  private String someProperty;

  private String anotherProperty;

...
/**
 * AllOfObjectWithDiscriminatorFirstProperty
 */
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AllOfObjectWithDiscriminatorFirstProperty {

  private String propertyType;

  private String someProperty;

...
/**
 * AllOfObjectWithDiscriminatorSecondProperty
 */
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AllOfObjectWithDiscriminatorSecondProperty {

  private String propertyType;

  private String anotherProperty;

...

We see that generation with a discriminator is no different from generation without a discriminator. Therefore, in the case of allOf, which is a composition tool within openapi, you can use both options.

5.1 AnyOf without discriminator

What does it look like in specifications:

AnyOfObjectWithoutDiscriminator:
      anyOf:
        - $ref: '#/components/schemas/AnyOfObjectWithoutDiscriminatorFirstProperty'
        - $ref: '#/components/schemas/AnyOfObjectWithoutDiscriminatorSecondProperty'
    AnyOfObjectWithoutDiscriminatorFirstProperty:
      type: object
      required:
        - someProperty
      properties:
        someProperty:
          type: string
    AnyOfObjectWithoutDiscriminatorSecondProperty:
      type: object
      required:
        - anotherProperty
      properties:
        anotherProperty:
          type: string

Generation result: AnyOfObjectWithoutDiscriminator, AnyOfObjectWithoutDiscriminatorFirstProperty, AnyOfObjectWithoutDiscriminatorSecondProperty

/**
 * AnyOfObjectWithoutDiscriminator
 */
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AnyOfObjectWithoutDiscriminator {

  private String someProperty;

  private String anotherProperty;

...
/**
 * AnyOfObjectWithoutDiscriminatorFirstProperty
 */
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AnyOfObjectWithoutDiscriminatorFirstProperty {

  private String someProperty;

...
/**
 * AnyOfObjectWithoutDiscriminatorSecondProperty
 */
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AnyOfObjectWithoutDiscriminatorSecondProperty {

  private String anotherProperty;

...

5.2 AnyOf with discriminator

What does it look like in specifications:

AnyOfObjectWithDiscriminator:
      anyOf:
        - $ref: '#/components/schemas/AnyOfObjectWithDiscriminatorFirstProperty'
        - $ref: '#/components/schemas/AnyOfObjectWithDiscriminatorSecondProperty'
      discriminator:
        propertyName: PropertyType
        mapping:
          first: '#AnyOfObjectWithDiscriminatorFirstProperty'
          second: '#AnyOfObjectWithDiscriminatorSecondProperty'
    AnyOfObjectWithDiscriminatorFirstProperty:
      type: object
      required:
        - propertyType
        - someProperty
      properties:
        propertyType:
          type: string
        someProperty:
          type: string
    AnyOfObjectWithDiscriminatorSecondProperty:
      type: object
      required:
        - propertyType
        - anotherProperty
      properties:
        propertyType:
          type: string
        anotherProperty:
          type: string

Generation result: AnyOfObjectWithDiscriminator, AnyOfObjectWithDiscriminatorFirstProperty, AnyOfObjectWithDiscriminatorSecondProperty

/**
 * AnyOfObjectWithDiscriminator
 */
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor

@JsonIgnoreProperties(
  value = "PropertyType", // ignore manually set PropertyType, it will be automatically generated by Jackson during serialization
  allowSetters = true // allows the PropertyType to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "PropertyType", visible = true)
@JsonSubTypes({
  @JsonSubTypes.Type(value = AnyOfObjectWithDiscriminatorFirstProperty.class, name = "first"),
  @JsonSubTypes.Type(value = AnyOfObjectWithDiscriminatorSecondProperty.class, name = "second"),
  @JsonSubTypes.Type(value = AnyOfObjectWithDiscriminatorFirstProperty.class, name = "AnyOfObjectWithDiscriminatorFirstProperty"),
  @JsonSubTypes.Type(value = AnyOfObjectWithDiscriminatorSecondProperty.class, name = "AnyOfObjectWithDiscriminatorSecondProperty")
})

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AnyOfObjectWithDiscriminator {

  private String propertyType;

  private String someProperty;

  private String anotherProperty;

...
/**
 * AnyOfObjectWithDiscriminatorFirstProperty
 */
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AnyOfObjectWithDiscriminatorFirstProperty {

  private String propertyType;

  private String someProperty;

...
/**
 * AnyOfObjectWithDiscriminatorSecondProperty
 */
@lombok.Builder(toBuilder = true)
@lombok.RequiredArgsConstructor
@lombok.AllArgsConstructor

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2023-11-19T15:45:21.742670500+03:00[Europe/Moscow]")
public class AnyOfObjectWithDiscriminatorSecondProperty {

  private String propertyType;

  private String anotherProperty;

...

The result is similar with oneOf. Moreover, if we use oneOf, validation limits the incoming request to one of the described options, while anyOf allows us to accept from one to all options.

6. PS

AnyOf, AllOf, OneOf can be combined and created quite complex designs to solve your problems.

Sometimes the same desired generation result can be achieved in different ways of describing the specification.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *