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.94k stars 6.03k forks source link

[JAVA] The java code generated for properties starting with two uppercase letters leads creates wrong json. #4051

Open schnabel opened 7 years ago

schnabel commented 7 years ago
Description

If the swagger declaration contains properties with names starting with two uppercase letters, the generated java code is not java bean conform. As a result jackson maps the properties twice. See: http://futuretask.blogspot.de/2005/01/java-tip-6-dont-capitalize-first-two.html

Swagger-codegen version

Tested with 2.2.1 and 2.2.2-SNAPSHOT

Swagger declaration file content or url
swagger: '2.0'
schemes:
  - http
basePath: '/v0.1/foo'
consumes:
  - 'application/json'
produces:
  - 'application/json'
paths:            
  '/foo':
    get:
      responses:
        200:
          description: "successful operation"
          schema:
            type: "array"
            items:
              $ref: "#/definitions/Pet"

definitions:

  Pet:
    description: >
      Pet
    type: object
    properties:
      ATT_Name:
        description: >
          Name of the pet
        type: string

generated java code:

/**
 * Pet 
 */
@ApiModel(description = "Pet ")
@javax.annotation.Generated(value = "class io.swagger.codegen.languages.JavaClientCodegen", date = "2016-10-22T10:58:27.662+02:00")
public class Pet   {
  @JsonProperty("ATT_Name")
  private String aTTName = null;

  public Pet aTTName(String aTTName) {
    this.aTTName = aTTName;
    return this;
  }

   /**
   * Name of the pet 
   * @return aTTName
  **/
  @ApiModelProperty(example = "null", value = "Name of the pet ")
  public String getATTName() {
    return aTTName;
  }

  public void setATTName(String aTTName) {
    this.aTTName = aTTName;
  }

  @Override
  public boolean equals(java.lang.Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Pet pet = (Pet) o;
    return Objects.equals(this.aTTName, pet.aTTName);
  }

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

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("class Pet {\n");

    sb.append("    aTTName: ").append(toIndentedString(aTTName)).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(java.lang.Object o) {
    if (o == null) {
      return "null";
    }
    return o.toString().replace("\n", "\n    ");
  }
}

jackson serialization result:

{"attname":"cat","ATT_Name":"cat"}
Command line used for generation

swagger-codegen-maven-plugin

Steps to reproduce

Build a jaxrs or spring server. Implement controller to return Pet array. Or use jackson mapper and serialize with the generated Pet model code.

Related issues

https://github.com/swagger-api/swagger-codegen/issues/445

Suggest a Fix

DefaultCodegen.java:

    /**
     * Camelize name (parameter, property, method, etc)
     *
     * @param word string to be camelize
     * @param lowercaseFirstLetter lower case for first letter if set to true
     * @return camelized string
     */
    public static String camelize(String word, boolean lowercaseFirstLetter) {
        if(lowercaseFirstLetter) {
            if(startsWithTwoUppercaseLetters(word)){
                word = word.substring(0, 2).toLowerCase() + word.substring(2);
            }
        }
        // Replace all slashes with dots (package separator)
        Pattern p = Pattern.compile("\\/(.?)");
        Matcher m = p.matcher(word);
        while (m.find()) {
            word = m.replaceFirst("." + m.group(1)/*.toUpperCase()*/); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
            m = p.matcher(word);
        }

        // case out dots
        String[] parts = word.split("\\.");
        StringBuilder f = new StringBuilder();
        for (String z : parts) {
            if (z.length() > 0) {
                f.append(Character.toUpperCase(z.charAt(0))).append(z.substring(1));
            }
        }
        word = f.toString();

        m = p.matcher(word);
        while (m.find()) {
            word = m.replaceFirst("" + Character.toUpperCase(m.group(1).charAt(0)) + m.group(1).substring(1)/*.toUpperCase()*/);
            m = p.matcher(word);
        }

        // Uppercase the class name.
        p = Pattern.compile("(\\.?)(\\w)([^\\.]*)$");
        m = p.matcher(word);
        if (m.find()) {
            String rep = m.group(1) + m.group(2).toUpperCase() + m.group(3);
            rep = rep.replaceAll("\\$", "\\\\\\$");
            word = m.replaceAll(rep);
        }

        // Remove all underscores
        p = Pattern.compile("(_)(.)");
        m = p.matcher(word);
        while (m.find()) {
            word = m.replaceFirst(m.group(2).toUpperCase());
            m = p.matcher(word);
        }

        if (lowercaseFirstLetter) {
            word = word.substring(0, 1).toLowerCase() + word.substring(1);
        }

        return word;
    }

    private static boolean startsWithTwoUppercaseLetters(String word) {
        boolean startsWithTwoUppercaseLetters = false;
        if(word.length() > 1) {
            startsWithTwoUppercaseLetters = word.substring(0, 2).equals(word.substring(0, 2).toUpperCase());
        }
        return startsWithTwoUppercaseLetters;
    }
schnabel commented 7 years ago

It is probably better to apply the patch in AbstractJavaCodegen toVarName(String) to avoid side effects in other generators.

wing328 commented 7 years ago

@schnabel I wonder if you've time to contribute a PR so that we can review the fix.

schnabel commented 7 years ago

@wing328 Yes, I can contribute a PR. There is another Problem with "all upper case properties" like URL or ATT_NAME. Do you need another issue for that?

wing328 commented 7 years ago

Ideally we prefer a separate issue for tracking purpose (if it's not too much work to open one)

schnabel commented 7 years ago

No that's ok. I can do that tomorrow. Do you want PRs on a fork?

wing328 commented 7 years ago

Yes, please fork this repo and create a branch for the change.

lehcim commented 5 years ago

If the swagger declaration contains properties with names starting with two uppercase letters, the generated java code is not java bean conform. As a result jackson maps the properties twice.

Jackson maps the property twice because the @JsonProperty annotation is set on the property attribute. If the annotation is moved to the property setter, there is only one mapping (in other words, this is a mustache problem).