Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] OpenAPI Generator fails to generate array models #7802

Open
5 of 6 tasks
segevfiner opened this issue Oct 25, 2020 · 29 comments
Open
5 of 6 tasks

[BUG] OpenAPI Generator fails to generate array models #7802

segevfiner opened this issue Oct 25, 2020 · 29 comments

Comments

@segevfiner
Copy link
Contributor

Bug Report Checklist

  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator (example)?
  • Have you tested with the latest master to confirm the issue still exists?
  • Have you searched for related issues/PRs?
  • What's the actual output vs expected output?
  • [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description

OpenAPI generator fails to generate array models. It complains with:

[main] INFO  o.o.codegen.DefaultGenerator - Model {} not generated since it's an alias to array (without property) and `generateAliasAsModel` is set to false (default)
openapi-generator version

v4.3.1

And also tried latest master using Docker 07c23f4.

OpenAPI declaration file content or url
openapi: '3.0.3'

info:
  title: Test
  version: 0.1.0

paths: {}

components:
  schemas:
    Test:
      type: array
      items:
        type: object
        properties:
          foo:
            type: string
Generation Details
docker run --rm \                                
  -v ${PWD}:/local openapitools/openapi-generator-cli generate \    
  -i /local/openapi.yaml \ 
  -g go \
  -o /local/out/go
Steps to reproduce
  1. Generate a client for any language using the provided schema. Note that the array model is not generated with the log message from this bug's description.
Related issues/PRs
Suggest a fix

The following code is erroneous. An array has items not properties (Even required by the OpenAPI spec). You might need to cast Schema to ArraySchema to get access to items or something like that.

if (!ModelUtils.isGenerateAliasAsModel(schema) && (schema.getProperties() == null || schema.getProperties().isEmpty())) {
// schema without property, i.e. alias to array
LOGGER.info("Model {} not generated since it's an alias to array (without property) and `generateAliasAsModel` is set to false (default)", name);
continue;

@auto-labeler
Copy link

auto-labeler bot commented Oct 25, 2020

👍 Thanks for opening this issue!
🏷 I have applied any labels matching special text in your issue.

The team will review the labels and make any necessary changes.

@wing328
Copy link
Member

wing328 commented Oct 25, 2020

Did you try setting the generateAliasAsModel option to true?

@segevfiner
Copy link
Contributor Author

It then generates models with just a plain map[string]interface{} (A generic type) instead of the nested type. This isn't an "alias model" that just aliases a plain array with no items type. I tried changing that if to:

diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java
index 24f407c08a..de212afa58 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java
@@ -26,6 +26,7 @@ import io.swagger.v3.oas.models.Paths;
 import io.swagger.v3.oas.models.info.Contact;
 import io.swagger.v3.oas.models.info.Info;
 import io.swagger.v3.oas.models.info.License;
+import io.swagger.v3.oas.models.media.ArraySchema;
 import io.swagger.v3.oas.models.media.Schema;
 import io.swagger.v3.oas.models.parameters.Parameter;
 import io.swagger.v3.oas.models.security.*;
@@ -482,9 +483,11 @@ public class DefaultGenerator implements Generator {
                         continue;
                     }
                 } else if (ModelUtils.isArraySchema(schema)) { // check to see if it's an "array" model
-                    if (!ModelUtils.isGenerateAliasAsModel(schema) && (schema.getProperties() == null || schema.getProperties().isEmpty())) {
-                        // schema without property, i.e. alias to array
-                        LOGGER.info("Model {} not generated since it's an alias to array (without property) and `generateAliasAsModel` is set to false (default)", name);
+                    if (!ModelUtils.isGenerateAliasAsModel(schema) && (((ArraySchema) schema).getItems() == null ||
+                            (!ModelUtils.isComposedSchema(((ArraySchema) schema).getItems()) &&
+                                ((ArraySchema) schema).getItems().getProperties() == null || ((ArraySchema) schema).getItems().getProperties().isEmpty()))) {
+                        // schema without items, i.e. alias to array
+                        LOGGER.info("Model {} not generated since it's an alias to array (without items) and `generateAliasAsModel` is set to false (default)", name);
                         continue;
                     }
                 }

But that only fixes it so that it isn't wrongly detected as an alias model. The inner items model is still not generated and used.

@romnus
Copy link

romnus commented Oct 27, 2020

Hi All, I believe this bug is causing my Apex code not to compile.

Like @segevfiner I get the
[main] INFO o.o.codegen.DefaultGenerator - Model {} not generated since it's an alias to array (without property) and generateAliasAsModel is set to false (default) warning. Then when I deploy my sandbox org, I get errors like Invalid type: OASOneOfnumberbooleanstringdate.

This error appears to originate from an attribute (not sure if that's the term?) in one of the API schemas (Open API 3.0):

          "cutsheetOptions": {
            "type": "array",
            "description": "List of options that meet the provided search criteria, if any.",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "value": {
                  "description": "Cutsheet property value.",
                  "oneOf": [
                    {
                      "type": "number"
                    },
                    {
                      "type": "boolean"
                    },
                    {
                      "type": "string"
                    },
                    {
                      "type": "string",
                      "format": "date"
                    }
                  ]
                },
                "modelParts": {
                  "type": "array",
                  "description": "A list of model parts that provide the given property value.",
                  "items": {
                    "type": "object",
                    "properties": {
                      "modelPart": {
                        "type": "string"
                      },
                      "option": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }

Setting generateAliasAsModel results in these errors and others.

It looks like the code generator is interpreting the oneOf attribute incorrectly? I'm new to all this so excuse any incoherence, but eager to figure out what the issue is.

@janekolszak
Copy link

Any news?

@ghost
Copy link

ghost commented Mar 19, 2021

A colleague and I were able to reproduce this with v3.3.4 and the latest v5.0.1 (deployed via the wget method, as per the installation instructions).

This use case seems to be right out of the Swagger examples (i.e. array of objects), so it seems odd that this error would arise with recent builds given it's likely a common/typical use case in OpenAPI-generated APIs/specs.

Edit: we were able to get it going by using a recent build (i.e. v5.0.1) plus the --generate-alias-as-model flag to the tool. The question remains: does it make sense that one should have to explicitly set said flag/option for such a use case?

Edit: nope: using the --generate-alias-as-model flag just makes the compilation errors go away. The actual auto-generated code is empty (i.e. blank schema).

@shimmerLum
Copy link

shimmerLum commented Mar 24, 2021

Hello,
I was having the same issue with Model {} not generated since it's an alias to array (without property) and `generateAliasAsModel` is set to false (default) when trying to generate a model for python-flask, so I also tried setting generateAliasAsModel when running the OpenAPI Generator.

I tested this with openapi-generator-cli-5.1.0-20210108.143511-2.jar

I used this command:

java -jar openapi-generator-cli-5.1.0-20210108.143511-2.jar generate -g python-flask -i test.yaml -o test_schema/ --generate-alias-as-model --additional-properties packageName=test

... with test.yaml as input. It's an example using the array type, which contains object types (this example is built following the examples from swagger.io for OpenAPI 3.0)

The warning message went away and the generator did generate a model in the following foldertest_schema/test/models - test_schema.py. However, as you can see, the model is wrong, as it did not populate it with the contents of my array (e.g. id, username, name).

The dictionary types are empty:

def __init__(self):  # noqa: E501
        """TestSchema - a model defined in OpenAPI
        """
        self.openapi_types = {
        }

        self.attribute_map = {
        }

I also created another example (using an example from here using just an object, not an array of objects, and the results I get match what I expected. I used test_object.yaml as input and this is the resulting model: test_schema.py

The command used:

java -jar openapi-generator-cli-5.1.0-20210108.143511-2.jar generate -g python-flask -i test_object.yaml -o test_object/  --additional-properties packageName=test

In this instance, the OpenAPI Generator successfully populated the model with the fields, getters and setters for them. Here is the constructor for the object:

    def __init__(self, id=None, username=None, name=None):  # noqa: E501
        """TestSchema - a model defined in OpenAPI
        :param id: The id of this TestSchema.  # noqa: E501
        :type id: int
        :param username: The username of this TestSchema.  # noqa: E501
        :type username: str
        :param name: The name of this TestSchema.  # noqa: E501
        :type name: str
        """
        self.openapi_types = {
            'id': int,
            'username': str,
            'name': str
        }

        self.attribute_map = {
            'id': 'id',
            'username': 'username',
            'name': 'name'
        }

        self._id = id
        self._username = username
        self._name = name

How should I best proceed to be able to generate a non-empty schema for the array-of-objects use case?
Thank you!

@ghost
Copy link

ghost commented Mar 24, 2021

@keysight-diana-belmega Thanks for putting this MCVE/PoC together Diana!

@DevSrSouza
Copy link
Contributor

Same issue here with Docker 1.41 spec of ContainerSummary when generating for Kotlin->Multiplatform

@shimmerLum
Copy link

shimmerLum commented Apr 9, 2021

Hello!

I found a workaround so that the OpenAPI generator generates valid models when an array is needed.

I tried to wrap the array in a top-level object, like in this example input file test_wrapper.yaml. You can see that the wrapper object is the only difference from my original example and the OpenAPI generator successfully created a good model, without using the --generate-alias-as-model option.

This worked with openapi-generator-cli-5.1.0-20210108.143511-2.jar. I didn't try it with other versions of the generator.

The command I used is:

java -jar openapi-generator-cli-5.1.0-20210108.143511-2.jar generate -g python-flask -i test_wrapper.yaml -o test_wrapper_schema/ --additional-properties packageName=test

...with test_wrapper.yaml as input file.

The files generated in the test_wrapper_schema/test/models folder are: test_schema.py and test_schema_array_wrapper.py, which work well.

You can see that test_schema.py contains a Python list of the generated object test_schema_array_wrapper.py, so it managed to create an object that contains the desired array:

def __init__(self, array_wrapper=None):  # noqa: E501
        """TestSchema - a model defined in OpenAPI
        :param array_wrapper: The array_wrapper of this TestSchema.  # noqa: E501
        :type array_wrapper: List[TestSchemaArrayWrapper]
        """
        self.openapi_types = {
            'array_wrapper': List[TestSchemaArrayWrapper]
        }

        self.attribute_map = {
            'array_wrapper': 'array-wrapper'
        }

        self._array_wrapper = array_wrapper

I don't know if this workaround works for any use-case, but I hope it helps anyone who needs it and maybe aids in solving the bug.

Thanks!

@kumar-tadepalli
Copy link

This is applicable for typescript-angular generator as well.

@mmihalev
Copy link

Same for PHP clients

@Sczlog
Copy link

Sczlog commented Nov 8, 2021

Same for Java Client

@stdNullPtr
Copy link

currently for generated spring boot controller that returns a list:

default ResponseEntity<List<Product>> getAllProducts() {
    getRequest().ifPresent(request -> {
        for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
            if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
                String exampleString = "{ \"price\" : 315.64, \"name\" : \"Vacuum cleaner\", \"description\" : \"Has reusable bags\" }";
                ApiUtil.setExampleResponse(request, "application/json", exampleString);
                break;
            }
        }
    });
    return new ResponseEntity<>(HttpStatus.valueOf(200));

}

The example response is not a list of 1 object, or a list of examples, but it's a single object which is wrong.
Is that related to this issues? How can I customize the response to be a list of the examples provided?

@arokanto
Copy link

arokanto commented Mar 8, 2022

Another dirty hack to get the generator to behave, at least on Node, is just to add Array's built-in length attribute as a property. Like this:

Something:
  type: array
  properties:
    length:
      type: integer
  items:
    $ref: '#/components/schemas/SomethingElse'

@blazergame
Copy link

Another dirty hack to get the generator to behave, at least on Node, is just to add Array's built-in length attribute as a property. Like this:

Something:
  type: array
  properties:
    length:
      type: integer
  items:
    $ref: '#/components/schemas/SomethingElse'

Can confirm this also worked with Springboot

@Macadoshis
Copy link

Macadoshis commented Apr 8, 2022

It's not a hack when you depend on an external yaml url whose another team is in charge of, and have no possible way of making them change "on the fly" declaration of inner items, to a dedicated new named schema through $ref...

1 year and a half and still not fixed... it's always worked with io.swagger.codegen.v3:swagger-codegen-maven-plugin, I'm seriously considering switching back 🙄

@gabslee
Copy link

gabslee commented Nov 25, 2022

i need to generate the array component, because i am using a Mapper to convert my classes in SpringBoot, and my response need to be an Array, cause the method findAll() that im using on ClassService.Java requires an Array, my ResponseEntity also requires an array and nothing worked :(

@Bsd15
Copy link

Bsd15 commented Dec 6, 2022

Another dirty hack to get the generator to behave, at least on Node, is just to add Array's built-in length attribute as a property. Like this:

Something:
  type: array
  properties:
    length:
      type: integer
  items:
    $ref: '#/components/schemas/SomethingElse'

Can confirm this also worked with Springboot

This generates a model with the length property only. and nothing else.

components:
  schemas:
    Cat:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: integer
        name:
          type: string
        description:
          type: string
        status:
            type: string
            enum:
              - available
              - adopted
              - dead
    Cats:
      type: array
      properties:
        length:
          type: integer
      items:
        $ref: '#/components/schemas/Cat'

Cats.java

package org.openapitools.model;

import java.net.URI;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonCreator;
import java.util.Arrays;
import org.openapitools.jackson.nullable.JsonNullable;
import java.util.NoSuchElementException;
import org.openapitools.jackson.nullable.JsonNullable;
import java.time.OffsetDateTime;
import javax.validation.Valid;
import javax.validation.constraints.*;
import io.swagger.v3.oas.annotations.media.Schema;


import java.util.*;
import javax.annotation.Generated;

/**
 * Cats
 */

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2022-12-06T13:01:52.635460+05:30[Asia/Kolkata]")
public class Cats {

  @JsonProperty("length")
  private JsonNullable<Object> length = JsonNullable.undefined();

  public Cats length(Object length) {
    this.length = JsonNullable.of(length);
    return this;
  }

  /**
   * Get length
   * @return length
  */
  
  @Schema(name = "length", required = false)
  public JsonNullable<Object> getLength() {
    return length;
  }

  public void setLength(JsonNullable<Object> length) {
    this.length = length;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Cats cats = (Cats) o;
    return equalsNullable(this.length, cats.length);
  }

  private static <T> boolean equalsNullable(JsonNullable<T> a, JsonNullable<T> b) {
    return a == b || (a != null && b != null && a.isPresent() && b.isPresent() && Objects.deepEquals(a.get(), b.get()));
  }

  @Override
  public int hashCode() {
    return Objects.hash(hashCodeNullable(length));
  }

  private static <T> int hashCodeNullable(JsonNullable<T> a) {
    if (a == null) {
      return 1;
    }
    return a.isPresent() ? Arrays.deepHashCode(new Object[]{a.get()}) : 31;
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("class Cats {\n");
    sb.append("    length: ").append(toIndentedString(length)).append("\n");
    sb.append("}");
    return sb.toString();
  }

  /**
   * Convert the given object to string with each line indented by 4 spaces
   * (except the first line).
   */
  private String toIndentedString(Object o) {
    if (o == null) {
      return "null";
    }
    return o.toString().replace("\n", "\n    ");
  }
}

Am I missing anything else that I should add?

@okaa-pi
Copy link

okaa-pi commented Feb 8, 2023

Any news on this issue? Still applicable in 2023.
I have the same result as Bsd15

EDIT:
I found a "workaround", which work well with springboot generator, you have to define the array as a schema, but the items aswell:

    TranslatedField:
      type: array
      items:
        $ref: "#/components/schemas/TranslatedFieldItem"
    TranslatedFieldItem:
      type: object
      required:
        - value
      properties:
        lang:
          $ref: "#/components/schemas/Language"
        value:
          type: string

@tony-ist
Copy link

tony-ist commented Mar 3, 2023

Can confirm this for typescript-fetch generator as well

@cpuchulanp
Copy link

Another dirty hack to get the generator to behave, at least on Node, is just to add Array's built-in length attribute as a property. Like this:

Something:
  type: array
  properties:
    length:
      type: integer
  items:
    $ref: '#/components/schemas/SomethingElse'

Can confirm this also worked with Springboot

@jonasheschl
Copy link
Contributor

Any update on this? Experiencing the same issue with aspnetcore.

@markbaydoun
Copy link

Also happening with Python, FastAPI and Pydantic v2. the generator worked correctly with the previous version of Pydantic though. Any thoughts?

@lostb1t
Copy link

lostb1t commented Nov 16, 2023

run into this issue aswell for rust

@matiazlarsson
Copy link

matiazlarsson commented Jan 12, 2024

Still seems to be present in v7.2.0 while generating arrays/lists in C#. Any updates?

@spacether
Copy link
Contributor

If you want array models to work without any special configuration you can try using the python and Java generators here which is a descendant of this code base

@thekhegay
Copy link

Any updates?

@jmartin127
Copy link

Running into this issue as well for Dart

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests