Forum Discussion

rcbiczok's avatar
rcbiczok
New Contributor
2 years ago

Using correct tag names in Model Resolver of swagger-core when using JAXB annotated Java POJOS

Hello Dear Swagger OS Tools Community,

 

We utilize a build process, where we generate JAXB-Annotated POJOs from XSD Schema files, then generate / serve OpenAPI files through Swagger / SpringDoc.

This is not the most optimal approach, but since we work with ISO 20022 messages in the banking sector that still works with XSD files rather than JSON Schema and friends, we have to stick with it.

 

Now I encountered on problem with the Model Resolver in the swagger-core for which I need your help / opinion. To illustrate this, lets consider our JAXB POJOS to look like this:

 

 

package io.swagger.v3.core.oas.models.xmltest;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "Relocation")
public class Relocation {
    @XmlElement(name = "Name")
    public String name;

    @XmlElement(name = "OldAddress")
    public Address oldAddress;

    @XmlElement(name = "NewAddress")
    public Address newAddress;
}

 

 

And:

 

 

package io.swagger.v3.core.oas.models.xmltest;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;

@XmlType(name = "RelocationAddress", propOrder = {
        "Street",
        "City"
})
public class ReolcationAddress {
    @XmlElement(name = "Street")
    public String street;

    @XmlElement(name = "City")
    public String city;
}

 

 

During model resolution, the Model resolver will automatically detect the relevant annotation and build an internal Schema object out of it:

 

 

{Relocation=class Schema {
    type: object
    format: null
    $ref: null
    description: null
    title: null
    multipleOf: null
    maximum: null
    exclusiveMaximum: null
    minimum: null
    exclusiveMinimum: null
    maxLength: null
    minLength: null
    pattern: null
    maxItems: null
    minItems: null
    uniqueItems: null
    maxProperties: null
    minProperties: null
    required: null
    not: null
    properties: {name=class StringSchema {
        class Schema {
            type: string
            format: null
            $ref: null
            description: null
            title: null
            multipleOf: null
            maximum: null
            exclusiveMaximum: null
            minimum: null
            exclusiveMinimum: null
            maxLength: null
            minLength: null
            pattern: null
            maxItems: null
            minItems: null
            uniqueItems: null
            maxProperties: null
            minProperties: null
            required: null
            not: null
            properties: null
            additionalProperties: null
            nullable: null
            readOnly: null
            writeOnly: null
            example: null
            externalDocs: null
            deprecated: null
            discriminator: null
            xml: class XML {
                name: Name
                namespace: null
                prefix: null
                attribute: null
                wrapped: null
            }
        }
    }, oldAddress=class Schema {
        type: null
        format: null
        $ref: #/components/schemas/RelocationAddress
        description: null
        title: null
        multipleOf: null
        maximum: null
        exclusiveMaximum: null
        minimum: null
        exclusiveMinimum: null
        maxLength: null
        minLength: null
        pattern: null
        maxItems: null
        minItems: null
        uniqueItems: null
        maxProperties: null
        minProperties: null
        required: null
        not: null
        properties: null
        additionalProperties: null
        nullable: null
        readOnly: null
        writeOnly: null
        example: null
        externalDocs: null
        deprecated: null
        discriminator: null
        xml: class XML {
            name: OldAddress
            namespace: null
            prefix: null
            attribute: null
            wrapped: null
        }
    }, newAddress=class Schema {
        type: null
        format: null
        $ref: #/components/schemas/RelocationAddress
        description: null
        title: null
        multipleOf: null
        maximum: null
        exclusiveMaximum: null
        minimum: null
        exclusiveMinimum: null
        maxLength: null
        minLength: null
        pattern: null
        maxItems: null
        minItems: null
        uniqueItems: null
        maxProperties: null
        minProperties: null
        required: null
        not: null
        properties: null
        additionalProperties: null
        nullable: null
        readOnly: null
        writeOnly: null
        example: null
        externalDocs: null
        deprecated: null
        discriminator: null
        xml: class XML {
            name: NewAddress
            namespace: null
            prefix: null
            attribute: null
            wrapped: null
        }
    }}
    additionalProperties: null
    nullable: null
    readOnly: null
    writeOnly: null
    example: null
    externalDocs: null
    deprecated: null
    discriminator: null
    xml: class XML {
        name: Relocation
        namespace: https://www.openapis.org/test/nested
        prefix: null
        attribute: null
        wrapped: null
    }
}, RelocationAddress=class Schema {
    type: object
    format: null
    $ref: null
    description: null
    title: null
    multipleOf: null
    maximum: null
    exclusiveMaximum: null
    minimum: null
    exclusiveMinimum: null
    maxLength: null
    minLength: null
    pattern: null
    maxItems: null
    minItems: null
    uniqueItems: null
    maxProperties: null
    minProperties: null
    required: null
    not: null
    properties: {street=class StringSchema {
        class Schema {
            type: string
            format: null
            $ref: null
            description: null
            title: null
            multipleOf: null
            maximum: null
            exclusiveMaximum: null
            minimum: null
            exclusiveMinimum: null
            maxLength: null
            minLength: null
            pattern: null
            maxItems: null
            minItems: null
            uniqueItems: null
            maxProperties: null
            minProperties: null
            required: null
            not: null
            properties: null
            additionalProperties: null
            nullable: null
            readOnly: null
            writeOnly: null
            example: null
            externalDocs: null
            deprecated: null
            discriminator: null
            xml: class XML {
                name: Street
                namespace: null
                prefix: null
                attribute: null
                wrapped: null
            }
        }
    }, city=class StringSchema {
        class Schema {
            type: string
            format: null
            $ref: null
            description: null
            title: null
            multipleOf: null
            maximum: null
            exclusiveMaximum: null
            minimum: null
            exclusiveMinimum: null
            maxLength: null
            minLength: null
            pattern: null
            maxItems: null
            minItems: null
            uniqueItems: null
            maxProperties: null
            minProperties: null
            required: null
            not: null
            properties: null
            additionalProperties: null
            nullable: null
            readOnly: null
            writeOnly: null
            example: null
            externalDocs: null
            deprecated: null
            discriminator: null
            xml: class XML {
                name: City
                namespace: null
                prefix: null
                attribute: null
                wrapped: null
            }
        }
    }}
    additionalProperties: null
    nullable: null
    readOnly: null
    writeOnly: null
    example: null
    externalDocs: null
    deprecated: null
    discriminator: null
    xml: null
}

 

 

As you can see, the model successfully extracts the necessary XML Object based on the XmlElement annotation and puts it into the properties list within the Relocation model. However, this information gets lost as soon as it generates an actual OAS Schema out of it: 

 

 

Relocation:
  type: object
  properties:
    name:
      type: string
    oldAddress:
      $ref: '#/components/schemas/RelocationAddress'
    newAddress:
      $ref: '#/components/schemas/RelocationAddress'
  xml:
    name: Relocation
    namespace: https://www.openapis.org/test/nested
RelocationAddress:
  type: object
  properties:
    street:
      type: string
      xml:
        name: Street
    city:
      type: string
      xml:
        name: City

 

 

So reading that schema would mean that the object of type 'Relocation' will have all upper case first letter except 'oldAddress' and 'newAddress', because the XML Object is missing there. This is, however, in-sync with the OAS specification, as it would forbid the use of an XML Object when '$ref' is used at the same time.
Just moving the XML Object from property to type definition would not solve it, as I use 'RelocationAddress' on two occasions with different Tag Names (OldAddress and NewAddress). This example might look trivial at first, but imagine you have something like 'CdtrAcc' and 'DbtrAcc' as XML tags and the actual Java properties are called 'creditorAccount' and 'debtorAccount'.

Now, my bold head came up with two potential approaches:


1) Duplicate the referenced type every time a conflict happens:

 

Relocation:
  type: object
  properties:
    name:
      type: string
    oldAddress:
      $ref: '#/components/schemas/OldAddress_RelocationAddress'
    newAddress:
      $ref: '#/components/schemas/NewAddress_RelocationAddress'
  xml:
    name: Relocation
    namespace: https://www.openapis.org/test/nested
OldAddress_RelocationAddress:
  type: object
  xml:
    name: OldAddress
  properties:
    street:
      type: string
      xml:
        name: Street
    city:
      type: string
      xml:
        name: City
NewAddress_RelocationAddress:
  type: object
  xml:
    name: NewAddress
  properties:
    street:
      type: string
      xml:
        name: Street
    city:
      type: string
      xml:
        name: City

 

 

 This would give the freedom to have different property names for specific tag names, but has also the disadvantage to potentially generate larger OAS files with unnecessary duplication.

 

2) Always use the Tag name as property name on collision:

 

Relocation:
  type: object
  properties:
    name:
      type: string
    OldAddress:
      $ref: '#/components/schemas/RelocationAddress'
    NewAddress:
      $ref: '#/components/schemas/RelocationAddress'
  xml:
    name: Relocation
    namespace: https://www.openapis.org/test/nested
RelocationAddress:
  type: object
  properties:
    street:
      type: string
      xml:
        name: Street
    city:
      type: string
      xml:
        name: City

 

We could tell the model resolver to just overwrite the property definition with its XML tag definition from the XmlElement annotation. The resulting model would be inconsequential for the API consumer, and the model generated from that OAS file would look like "normal code'. The only drawback is that the original 'property" information gets lost during this process. On the other hand, that information might not be that useful anyway from an API design standpoint.

 

I would personally love to see option 2) implemented, or at least somehow an option to control this generation. Please recall that since we generate JAXB POJOs from an XSD file, we don't have that much of control on the Java property names generated from the XJC tool.

 

I welcome any alternative suggestion.

No RepliesBe the first to reply