Как десериализовать тело запроса как дочерние классы?

#c# #asp.net-mvc #openapi

#c# #asp.net-mvc #openapi

Вопрос:

У нас есть API, определенный спецификацией OpenAPI (v3). Существует класс Animal, от которого наследуют Cat и Dog. Когда сгенерированный клиент C # выполняет запрос, скажем, для List<Animal> , они правильно десериализуются как дочерние классы. Однако, если мы попытаемся ОПУБЛИКОВАТЬ List<Animal> , содержащий Cats и Dogs, сгенерированный C # ASP.NET серверная часть получает их только как Animals вместо дочерних классов. Когда мы отправляем Cat с помощью Postman, серверная часть получает Animal, поэтому я уверен, что это ASP.NET проблема с генерацией.

Spec.yaml — это:

 openapi: 3.0.0
paths:
  /saveData:
    post:
      description: Send all the data required
      parameters:
        - name: id
          in: query
          required: true
          description: The id of the animal
          schema:
            type: string
            format: uuid
            example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SaveData'
      responses:
        200:
          description: The animal
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Animal'
        401:
          $ref: '#/components/responses/Unauthorised'
        400:
          $ref: '#/components/responses/BadRequest'

components:
  schemas:
    SaveData:
      type: object
      required:
        - animals
      properties:
        animals:
          type: array
          description: A list of animals
          items:
            $ref: '#/components/schemas/Animal'

    Animal:
      type: object
      discriminator:
        propertyName: className
      required:
        - "id"
        - "name"
        - "className"
      properties:
        id:
          type: string
          description: The unique ID of this object
          format: uuid
          example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
        name:
          type: string
          description: Gives a name to the animal
          example: Bob
        className:
          type: string
          description: Determines which child class this object is
          example: Cat

    Cat:
      allOf:
        - $ref: '#/components/schemas/Animal'

    Dog:
      allOf:
        - $ref: '#/components/schemas/Animal'
        - type: object
          required:
            - "breed"
          properties:
            breed:
              type: string
              description: The breed of dog
              example: Terrier
              nullable: true
  

Примером тела запроса может быть:

 {
    "animals": [
        {
            "id": "84d40807-4c68-4b41-9c24-619847e80269",
            "name": "Bob",
            "className": "Cat"
        },
        {
            "id": "67a4b35e-4fc3-4a67-a77a-2a032640559f",
            "name": "Spot",
            "breed": "Husky",
            "className": "Dog"
        }
    ]
}
  

Они оба десериализуются как Animal, поэтому Dog теряет свои дочерние свойства.

Как мы можем настроить определение или генерацию, чтобы эти классы были должным образом десериализованы на стороне сервера?

Комментарии:

1. Опубликуйте свой код — классы, клиентский код, который создает этот post, действие MVC, которое получает post.

Ответ №1:

Это оказалось очень простым решением. Генерация кода на стороне клиента включала JsonSubtypes атрибуты, необходимые для правильной десериализации.

В приведенном выше примере начало класса Animal должно выглядеть следующим образом:

 using JsonSubTypes;

[JsonConverter(typeof(JsonSubtypes), "className")]
[JsonSubtypes.KnownSubType(typeof(Cat), "Cat")]
[JsonSubtypes.KnownSubType(typeof(Dog), "Dog")]
public partial class Animal
{
    // Class stuff here
}
  

Мне также пришлось запустить dotnet add .myproject.csproj package JsonSubTypes , чтобы получить пакет.