NSwag: oneOf is not produce the right results for C# generated classes?

When trying to generate an API client for ShipEngine, NSwag is having issues when using oneOf to combine related entities to indicate that the request can have one of the two values. It ends up just bringing in the type for the first value, but the second is never included. Here is the simplified yaml file:

openapi: 3.0.0
info:
  title: oneOf bug
  version: 1.0
components:
  schemas:
    se_id:
      title: se_id
      type: string

    calculate_rates_request_body:
      title: calculate_rates_request_body
      type: object
      required:
        - shipment_id
        - shipment
      additionalProperties: false
      oneOf:
        - $ref: '#/components/schemas/shipment_id_request'
        - $ref: '#/components/schemas/shipment_request'

    shipment_id_request:
      title: shipment_id_request
      type: object
      additionalProperties: false
      properties:
        shipment_id:
          allOf:
            - $ref: '#/components/schemas/se_id'

    shipment_request:
      title: shipment_request
      type: object
      additionalProperties: false
      properties:
        shipment:
          allOf:
            - $ref: '#/components/schemas/address_validating_shipment'

    address_validating_shipment:
      title: address_validating_shipment
      type: object
      required:
        - shipment_id
        - carrier_id
      additionalProperties: false
      properties:
        shipment_id:
          allOf:
            - $ref: '#/components/schemas/se_id'
        carrier_id:
          allOf:
            - $ref: '#/components/schemas/se_id'

The C# class generated is the following. Note that the it only contains the base ShipmentId field, and not the second Shipment field which the schema happily supports.

    public partial class CalculateRatesRequestBody 
    {
        public string ShipmentId { get; set; }
    }
    
    public partial class ShipmentRequest 
    {
        public AddressValidatingShipment Shipment { get; set; }
    }
    
    public partial class AddressValidatingShipment 
    {
        public string ShipmentId { get; set; }
        public string CarrierId { get; set; }
    }

What I would have expected to be generated is the following:

    public partial class CalculateRatesRequestBody
    {
        public string ShipmentId { get; set; }
        public AddressValidatingShipment Shipment { get; set; }
    }

    public partial class AddressValidatingShipment
    {
        public string ShipmentId { get; set; }
        public string CarrierId { get; set; }
    }

If I change it to be allOf rather than oneOf, I get the following which does work. But in reality the spec is correct in saying it’s a oneOf and not allOf, because only one can be sent in the request at a time, not all of them:

    public partial class CalculateRatesRequestBody : ShipmentIdRequest
    {
    }
    
    public partial class ShipmentIdRequest 
    {
        public string ShipmentId { get; set; }
    }
    
    public partial class ShipmentRequest 
    {
        public AddressValidatingShipment Shipment { get; set; }
    }
    
    public partial class AddressValidatingShipment 
    {
        public string ShipmentId { get; set; }
        public string CarrierId { get; set; }
    }

So it seems to me when generating a C# class for this specification, we should really be generating the same schema as the allOf case. Any ideas on the best way to fix this in the code? I assume it’s probably in the JsonSchema rendering code, not NSwag itself?

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 7
  • Comments: 34 (14 by maintainers)

Most upvoted comments

oneOf can be accomodated by polymorphism. From what I understood here allOf is already supported. anyOf could be potentially supported the same way as oneOf with polymorphism with having different requirements for each class while they are also merged together, but having a validation logic would seem better at that point.

Isn’t this pretty major issue if NSwag doesn’t really work properly with the standard? Can this issue get a bit more traction?

We eventually figured out that openapi-generator can generate working code from oneOf elements. It took a couple of days to bump into the right parameters on a revisit (they were a bit hidden in the documentation). Our guy used netcore generator with a targetFramework of net4.7 and all was well.

I also realized that this stackoverflow answer (“replace the oneOf with a reference to an abstract type”) was a viable workaround that might work with generators like this one (it did with openapi-generator java generator).

Yeah, I changed my IDE to only trim whitespace on the lines I edit now for open source projects for that reason. It used to change every line (nice for our own projects, annoying for open source ones!). I usually use the perforce diff tool which allows me to view changes and ignore whitespace.

Support for oneOf, anyOf and allOf is not very easy for strongly typed languages like C# that doesn’t have discriminated unions. If we want to make it in one property, we could create a base class Base and make the subtypes inherit that base type, but we wouldn’t be able to support built-in types like MyType | string (which can easily be done in typescript for example) We could make it an object, but that would not be strongly typed for the C# compiler.

I’m currently trying to create an experimental OpenApi to C# generator, and the oneOf/anyOf/allOf issues are on my list. I currently made an attempt like this : OneOf<TLeft, TRight> With this kind of properties and the correct JsonConverter attribute, I think I can serialize/deserialize those kind of properties, but I’m not sure how well it would play with NSwag, because I’m currently targetting net50 and System.Text.Json.

Stay tuned…

My fork if nswag works for the code generation side. I forget if I have my own nuget package or if I build it all as one project.

https://github.com/kendallb/NSwag