0

Anyone knows if there's any good online site for validating JSON messages against an OpenAPI 3.0.x document?

I've got the following signature for a path:

 /api/equipamentos:
    post:
...
    put:
      requestBody:
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/MsgAtualizacaoComputador'
                - $ref: '#/components/schemas/MsgAtualizacaoEquipamentoAtivo'
                - $ref: '#/components/schemas/MsgAtualizacaoEquipamentoGenerico'
                - $ref: '#/components/schemas/MsgAtualizacaoImpressora'
                - $ref: '#/components/schemas/MsgAtualizacaoSoftware'
...

Where the types look like this:

    MsgAtualizacaoEquipamento:
      required:
        - $type
      type: object
      properties:
        $type:
          type: string
        serialNumber:
          type: string
          nullable: true
        tag:
          type: string
          nullable: true
        modelo:
          type: string
          nullable: true
        username:
          type: string
          nullable: true
        partNumber:
          type: string
          nullable: true
        idFabricante:
          type: integer
          format: int32
        dataAquisicao:
          type: string
          format: date-time
        dataFimGarantia:
          type: string
          format: date-time
        data:
          type: string
          format: date-time
        idFornecedor:
          type: integer
          format: int32
        observacoes:
          type: string
          nullable: true
        estadoEquipamento:
          $ref: '#/components/schemas/EstadoEquipamento'
        idLocalTrabalho:
          type: integer
          format: int32
        idFuncionario:
          type: integer
          format: int32
          nullable: true
        localizacaoArmazem:
          type: string
          nullable: true
        importGuid:
          type: string
          format: uuid
        etiquetaImpressaEm:
          type: string
          format: date-time
          nullable: true
        dataRetoma:
          type: string
          format: date-time
          nullable: true
        idImportacao:
          type: integer
          format: int32
        dadosGerfip:
          $ref: '#/components/schemas/DadosGerfipv2'
        id:
          type: integer
          format: int32
        version:
          type: integer
          format: int32
      additionalProperties: false
      discriminator:
        propertyName: $type
        mapping:
          Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoComputador, Sra.Assistencias.Dtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoComputador'
          Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoEquipamentoAtivo, Sra.Assistencias.Dtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoEquipamentoAtivo'
          Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoEquipamentoGenerico, Sra.Assistencias.Dtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoEquipamentoGenerico'
          Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoImpressora, Sra.Assistencias.Dtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoImpressora'
          Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoSoftware, Sra.Assistencias.Dtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoSoftware'
    MsgAtualizacaoEquipamentoAtivo:
      type: object
      allOf:
        - $ref: '#/components/schemas/MsgAtualizacaoEquipamento'
      properties:
        nome:
          type: string
          nullable: true
        numPortas:
          type: integer
          format: int32
        ip:
          type: string
          nullable: true
        velocidade:
          type: string
          nullable: true
        idTipoEquipamentoAtivo:
          type: integer
          format: int32
      additionalProperties: false
    MsgAtualizacaoEquipamentoComEquipamentosAssociados:
      required:
        - $type
      type: object
      properties:
        $type:
          type: string
        serialNumber:
          type: string
          nullable: true
        tag:
          type: string
          nullable: true
        modelo:
          type: string
          nullable: true
        username:
          type: string
          nullable: true
        partNumber:
          type: string
          nullable: true
        idFabricante:
          type: integer
          format: int32
        dataAquisicao:
          type: string
          format: date-time
        dataFimGarantia:
          type: string
          format: date-time
        data:
          type: string
          format: date-time
        idFornecedor:
          type: integer
          format: int32
        observacoes:
          type: string
          nullable: true
        estadoEquipamento:
          $ref: '#/components/schemas/EstadoEquipamento'
        idLocalTrabalho:
          type: integer
          format: int32
        idFuncionario:
          type: integer
          format: int32
          nullable: true
        localizacaoArmazem:
          type: string
          nullable: true
        importGuid:
          type: string
          format: uuid
        etiquetaImpressaEm:
          type: string
          format: date-time
          nullable: true
        dataRetoma:
          type: string
          format: date-time
          nullable: true
        idImportacao:
          type: integer
          format: int32
        dadosGerfip:
          $ref: '#/components/schemas/DadosGerfipv2'
        id:
          type: integer
          format: int32
        version:
          type: integer
          format: int32
        idsEquipamentosAssociar:
          type: array
          items:
            $ref: '#/components/schemas/EquipamentoInfo'
          nullable: true
        idsEquipamentosCancelar:
          type: array
          items:
            $ref: '#/components/schemas/EquipamentoInfo'
          nullable: true
        idsEquipamentosPropagar:
          type: array
          items:
            $ref: '#/components/schemas/EquipamentoInfo'
          nullable: true
      additionalProperties: false
      discriminator:
        propertyName: $type
        mapping:
          Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoComputador, Sra.Assistencias.Dtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoComputador'
          Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoSoftware, Sra.Assistencias.Dtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoSoftware'
    MsgAtualizacaoEquipamentoGenerico:
      type: object
      allOf:
        - $ref: '#/components/schemas/MsgAtualizacaoEquipamento'
      properties:
        idTipoEquipamentoGenerico:
          type: integer
          format: int32
      additionalProperties: false
    MsgAtualizacaoEquipamentos:
      type: object
      properties:
        observacoes:
          type: string
          nullable: true
        bytes:
          type: string
          format: byte
          nullable: true
      additionalProperties: false
    MsgAtualizacaoFuncionario:
      type: object
      properties:
        idFuncionario:
          type: integer
          format: int32
        version:
          type: integer
          format: int32
        nif:
          type: string
          nullable: true
        idLocalTrabalho:
          type: integer
          format: int32
        nome:
          type: string
          nullable: true
        carreira:
          type: string
          nullable: true
        numeroMecanografico:
          type: integer
          format: int32
        codUnidadeOrganica:
          type: integer
          format: int32
        situacaoProfissional:
          $ref: '#/components/schemas/SituacaoProfissional'
        contactos:
          type: array
          items:
            $ref: '#/components/schemas/Contacto'
          nullable: true
        propagaLocalTrabalhoParaEquipamentos:
          type: boolean
      additionalProperties: false
    MsgAtualizacaoImpressora:
      type: object
      allOf:
        - $ref: '#/components/schemas/MsgAtualizacaoEquipamento'
      properties:
        idTipoImpressora:
          type: integer
          format: int32
        ip:
          type: string
          nullable: true
        nomeNetBIOS:
          type: string
          nullable: true
      additionalProperties: false
    MsgAtualizacaoSoftware:
      type: object
      allOf:
        - $ref: '#/components/schemas/MsgAtualizacaoEquipamentoComEquipamentosAssociados'
      properties:
        numLicencasAdquiridas:
          type: integer
          format: int32
        nome:
          type: string
          nullable: true
        versao:
          type: string
          nullable: true
        upgrade:
          type: boolean
      additionalProperties: false

We simply can't pass any message without getting validation errors. For instance, here's a sample which can't pass the validation test:

{
    "$type": "Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoComputador, Sra.Assistencias.Dtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
    "nomeNetBIOS": "XPTO",
    "ip": "",
    "processador": "IU7-155H",
    "memoria": "16GB",
    "disco": "512GB",
    "gateway": "",
    "idTipoComputador": 3,
    "idsEquipamentosAssociar": [],
    "idsEquipamentosCancelar": [],
    "idsEquipamentosPropagar": [],
    "id": 83695,
    "version": 6,
    "serialNumber": "PWOCWVQD",
    "tag": "TAG000082196",
    "modelo": "Lenovo too",
    "username": "",
    "partNumber": "21MR004BPG",
    "idFabricante": 153,
    "dataAquisicao": "2024-12-16T00:00:00+00:00",
    "dataFimGarantia": "2027-12-15T00:00:00+00:00",
    "data": "0001-01-01T00:00:00",
    "idFornecedor": 57,
    "observacoes": "None",
    "estadoEquipamento": 4,
    "idLocalTrabalho": 807,
    "idFuncionario": 35112,
    "localizacaoArmazem": "",
    "importGuid": "00000000-0000-0000-0000-000000000000",
    "etiquetaImpressaEm": "2025-01-07T10:27:25.6946923+00:00",
    "dataRetoma": null,
    "idImportacao": 0,
    "dadosGerfip": {
        "$type": "Sra.Assistencias.Dtos.Equipamentos.DadosGerfipv2, Sra.Assistencias.Dtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
        "procedimentoAquisitivo": "INTERREG VIU-D MAC 2121-2027",
        "numeroInventario": "1000310010",
        "dataImportacaoInventario": null
    }
}

I've already checked and triple checked the JSON message and I can't see anything wrong with it. Any ideas on what's going on?

1 Answer 1

0

Seems you have run into a very common mistake with JSON Schema, particularly older draft versions like the one used in OpenAPI 3.0.x (JSON Schema draft-04)

allOf and additionalProperties: false don't really play nice together.

You can see an explanation here https://stackoverflow.com/a/79302728/8564731

Furthermore, your allOf construction is malformed, if I am interpreting your intent correctly. All subschemas with an allOf should be inside the array. You have defined allOf and a sibling properties keyword, which is valid JSON Schema, but I don't think that's how you were intending to use it.

    allOf:
        - $ref: '#/components/schemas/MsgAtualizacaoEquipamento'
      properties:
        nome:
          type: string
          nullable: true
        numPortas:
          type: integer
          format: int32
        ip:
          type: string
          nullable: true
        velocidade:
          type: string
          nullable: true
        idTipoEquipamentoAtivo:
          type: integer
          format: int32
      additionalProperties: false
     allOf:
        - $ref: "#/definitions/schemas/MsgAtualizacaoEquipamento"
        - properties: # << this should be part of the array
            nome:
              type: string
              nullable: true
            numPortas:
              type: integer
              format: int32
            ip:
              type: string
              nullable: true
            velocidade:
              type: string
              nullable: true
            idTipoEquipamentoAtivo:
              type: integer
              format: int32

additionalProperties: false should be removed from your schemas. If you really want to constraint your schemas not to allow additional properties, you need to follow the advice of the post I referenced, where you must redefine every possible keyword at the root of the schema. Then you can apply additionalProperties: false.


The last issue, after you fix those items is the discriminator mapping should be a sibling to the oneOf in your request body.

The example provided doesn't match the available schemas you have given us so I had to modify it to make it work correctly.

There are quite a few missing schemas to make it function 100%, but hopefully that gives you an idea of how to fix it.

openapi: 3.0.4
info:
  title: test
  version: 1.0.0
servers: []
paths:
  /thing:
    post:
      summary: a request
      responses:
        '200':
          description: OK
      requestBody:
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/MsgAtualizacaoEquipamentoAtivo'
                - $ref: '#/components/schemas/MsgAtualizacaoEquipamentoGenerico'
                - $ref: '#/components/schemas/MsgAtualizacaoImpressora'
                - $ref: '#/components/schemas/MsgAtualizacaoSoftware'
              discriminator:
                propertyName: $type
                mapping:
                  Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoEquipamentoAtivo, Sra.Assistencias.Dtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoEquipamentoAtivo'
                  Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoEquipamentoGenerico, Sra.Assistencias.Dtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoEquipamentoGenerico'
                  Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoImpressora, Sra.Assistencias.Dtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoImpressora'
                  Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoSoftware, Sra.Assistencias.Dtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoSoftware'
            examples:
              test:
                value:
                  {
                    '$type': 'Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoEquipamentoAtivo, Sra.Assistencias.Dtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null',
                    'nomeNetBIOS': 'XPTO',
                    'ip': '',
                    'processador': 'IU7-155H',
                    'memoria': '16GB',
                    'disco': '512GB',
                    'gateway': '',
                    'idTipoComputador': 3,
                    'idsEquipamentosAssociar': [],
                    'idsEquipamentosCancelar': [],
                    'idsEquipamentosPropagar': [],
                    'id': 83695,
                    'version': 6,
                    'serialNumber': 'PWOCWVQD',
                    'tag': 'TAG000082196',
                    'modelo': 'Lenovo too',
                    'username': '',
                    'partNumber': '21MR004BPG',
                    'idFabricante': 153,
                    'dataAquisicao': '2024-12-16T00:00:00+00:00',
                    'dataFimGarantia': '2027-12-15T00:00:00+00:00',
                    'data': '0001-01-01T00:00:00',
                    'idFornecedor': 57,
                    'observacoes': 'None',
                    'estadoEquipamento': 4,
                    'idLocalTrabalho': 807,
                    'idFuncionario': 35112,
                    'localizacaoArmazem': '',
                    'importGuid': '00000000-0000-0000-0000-000000000000',
                    'etiquetaImpressaEm': '2025-01-07T10:27:25.6946923+00:00',
                    'dataRetoma': null,
                    'idImportacao': 0,
                    'dadosGerfip':
                      {
                        '$type': 'Sra.Assistencias.Dtos.Equipamentos.DadosGerfipv2, Sra.Assistencias.Dtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null',
                        'procedimentoAquisitivo': 'INTERREG VIU-D MAC 2121-2027',
                        'numeroInventario': '1000310010',
                        'dataImportacaoInventario': null,
                      },
                  }
components:
  schemas:
    MsgAtualizacaoEquipamento:
      required:
        - $type
      type: object
      properties:
        $type:
          type: string
        serialNumber:
          type: string
          nullable: true
        tag:
          type: string
          nullable: true
        modelo:
          type: string
          nullable: true
        username:
          type: string
          nullable: true
        partNumber:
          type: string
          nullable: true
        idFabricante:
          type: integer
          format: int32
        dataAquisicao:
          type: string
          format: date-time
        dataFimGarantia:
          type: string
          format: date-time
        data:
          type: string
          format: date-time
        idFornecedor:
          type: integer
          format: int32
        observacoes:
          type: string
          nullable: true
        estadoEquipamento: {}
        idLocalTrabalho:
          type: integer
          format: int32
        idFuncionario:
          type: integer
          format: int32
          nullable: true
        localizacaoArmazem:
          type: string
          nullable: true
        importGuid:
          type: string
          format: uuid
        etiquetaImpressaEm:
          type: string
          format: date-time
          nullable: true
        dataRetoma:
          type: string
          format: date-time
          nullable: true
        idImportacao:
          type: integer
          format: int32
        dadosGerfip: {}
        id:
          type: integer
          format: int32
        version:
          type: integer
          format: int32
    MsgAtualizacaoEquipamentoAtivo:
      type: object
      allOf:
        - $ref: '#/components/schemas/MsgAtualizacaoEquipamento'
        - properties:
            nome:
              type: string
              nullable: true
            numPortas:
              type: integer
              format: int32
            ip:
              type: string
              nullable: true
            velocidade:
              type: string
              nullable: true
            idTipoEquipamentoAtivo:
              type: integer
              format: int32
    MsgAtualizacaoEquipamentoComEquipamentosAssociados:
      required:
        - $type
      type: object
      properties:
        $type:
          type: string
        serialNumber:
          type: string
          nullable: true
        tag:
          type: string
          nullable: true
        modelo:
          type: string
          nullable: true
        username:
          type: string
          nullable: true
        partNumber:
          type: string
          nullable: true
        idFabricante:
          type: integer
          format: int32
        dataAquisicao:
          type: string
          format: date-time
        dataFimGarantia:
          type: string
          format: date-time
        data:
          type: string
          format: date-time
        idFornecedor:
          type: integer
          format: int32
        observacoes:
          type: string
          nullable: true
        estadoEquipamento: {}
        idLocalTrabalho:
          type: integer
          format: int32
        idFuncionario:
          type: integer
          format: int32
          nullable: true
        localizacaoArmazem:
          type: string
          nullable: true
        importGuid:
          type: string
          format: uuid
        etiquetaImpressaEm:
          type: string
          format: date-time
          nullable: true
        dataRetoma:
          type: string
          format: date-time
          nullable: true
        idImportacao:
          type: integer
          format: int32
        dadosGerfip: {}
        id:
          type: integer
          format: int32
        version:
          type: integer
          format: int32
        idsEquipamentosAssociar:
          type: array
          items: {}
          nullable: true
        idsEquipamentosCancelar:
          type: array
          items: {}
          nullable: true
        idsEquipamentosPropagar:
          type: array
          items: {}
          nullable: true
      discriminator:
        propertyName: $type
        mapping:
          Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoComputador, Sra.Assistencias.Dtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoComputador'
          Sra.Assistencias.Dtos.Equipamentos.MsgAtualizacaoSoftware, Sra.Assistencias.Dtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null: '#/components/schemas/MsgAtualizacaoSoftware'
    MsgAtualizacaoEquipamentoGenerico:
      type: object
      allOf:
        - $ref: '#/components/schemas/MsgAtualizacaoEquipamento'
        - properties:
            idTipoEquipamentoGenerico:
              type: integer
              format: int32
    MsgAtualizacaoEquipamentos:
      type: object
      properties:
        observacoes:
          type: string
          nullable: true
        bytes:
          type: string
          format: byte
          nullable: true
    MsgAtualizacaoFuncionario:
      type: object
      properties:
        idFuncionario:
          type: integer
          format: int32
        version:
          type: integer
          format: int32
        nif:
          type: string
          nullable: true
        idLocalTrabalho:
          type: integer
          format: int32
        nome:
          type: string
          nullable: true
        carreira:
          type: string
          nullable: true
        numeroMecanografico:
          type: integer
          format: int32
        codUnidadeOrganica:
          type: integer
          format: int32
        situacaoProfissional: {}
        contactos:
          type: array
          items: {}
          nullable: true
        propagaLocalTrabalhoParaEquipamentos:
          type: boolean
      additionalProperties: false
    MsgAtualizacaoImpressora:
      type: object
      allOf:
        - $ref: '#/components/schemas/MsgAtualizacaoEquipamento'
        - properties:
            idTipoImpressora:
              type: integer
              format: int32
            ip:
              type: string
              nullable: true
            nomeNetBIOS:
              type: string
              nullable: true
    MsgAtualizacaoSoftware:
      type: object
      properties:
        numLicencasAdquiridas:
          type: integer
          format: int32
        nome:
          type: string
          nullable: true
        versao:
          type: string
          nullable: true
        upgrade:
          type: boolean
    MsgAtualizacaoComputador: {}

Here's a sample of it working correctly

redoc

Sign up to request clarification or add additional context in comments.

5 Comments

Hello Jeremy. I didn't generate tje schema by hand...it was generated automatically Swashbuckle for an aspnet core 9 web api. The put request expects a MsgAtualizacaoEquipamento derived type and I thought that was correctly exosed by this openapi doc. It seems like I wqs completely wrong. I'll take a deep look at all the points you've mentioned and I'll come back with feedback. thanks a lot for your valuable help.
Hello again. Built a small sample with only the types used by this specific call. Haven't tested against the firewall's validator, but I've noticed that now derived's types allOf do indeed generate an array instead of what I got at work. So, there's definetely something wrong with the way that Swashbuckle is generating the open api for the real app...I'll try to find out what's going on...
@jeremie-fiel, regarding your last input about the discriminator, I'm looking at the spec the doc should conform with (open api 3.0.1): spec.openapis.org/oas/v3.0.1.html#discriminator-object. You say that discrimantor should be a sibling to the oneOf. The spec does mention that, but it also says that in order to avoid redundancy, the discriminator may be added to the parent schema and all schema in an allOf may be used as an alternate schema. That being the case, I'm under the impression that the last change you mentioned isn't required, right? Thanks.
This entire section was rewritten and clarified quite a few points around discriminator. spec.openapis.org/oas/v3.0.4.html#discriminator-object The version change is only information and fully backwards compatible with your OAS document. One thing to note is the allOf form mentioned there has an explicit warning that it does not apply to validation scenarios, which is what you are trying to do. I believe the way I've written it is what you are trying to achieve.
You're right...well, I think I've applied all your points, but still doesn't work. I've started a new post about this: stackoverflow.com/questions/79408470/…. It has the complete code for the open api and json msg which seem to break it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.