NSwag generated C# client: Open API property name clashes and decimal types rather than double

John Reilly October 31, 2021
Source

NSwag is a great tool for generating client libraries in Cand TypeScript from Open API / Swagger definitions. You can face issues where Open API property names collide due to the nature of the Clanguage, and when you want to use decimal for your floating point numeric type over double. This post demonstrates how to get over both issues.

Make a CClient Generator

Let's get a console app set up that will allow us to generate a Cclient using an Open API file:

We'll also add a petstore-simple.json file to our project which we'll borrow from https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v2.0/json/petstore-simple.json (home of the Open API specification):

We'll tweak our NSwag.csproj file to ensure that the json file is included in our build output:

This will give us a console app with a reference to NSwag. Now we'll flesh out the Program.cs file thusly:

If we perform a dotnet run we now pump out a GeneratedClient.cs file which is a Cclient library for the pet store. Fabulous.

So far so dandy. We're taking an Open API json file and generating a Cclient library from it.

When properties collide

It's time to break things. We're presently generating a Pet class that looks like this:

We're going to take our Pet definition in the petstore-simple.json file, and add a new @id property alongside the id property:

For why? Whilst this may seem esoteric, this is a scenario that can present. It's not unknown to encounter properties which are identical, save for an @ prefix. This is often the case for meta-properties.

What do we get if we run our generator over that?

We get code that doesn't compile. You can't have two properties in a Cclass with the same name. You also cannot have @ as a character in a Cproperty or variable name. To quote the docs:

The @ special character serves as a verbatim identifier.

It so happens that, by default, NSwag purges @ characters from property names. If there isn't another property which is named the same save for an @ prefix, this is a fine strategy. If there is, as for us now, you're toast.

There's a workaround. We'll create a new HandleAtCSharpPropertyNameGenerator class:

This is a replacement for the CSharpPropertyNameGenerator that NSwag ships with. Rather than purging the @ character, it replaces usage with a double underscore: __.

We'll make use of our new PropertyNameGenerator:

With this in place, when we dotnet run we create a class that looks like this:

So the newly generated property name is __id rather than the clashing Id. Rather wonderfully, this works. It resolves the issue we faced. We've chosen to use __ as our prefix - we could choose something else if that worked better for us.

Knowing that this hook exists is super useful.

Use decimal not double for floating point numbers

Another common problem with generated Cclients is the number type used to represent floating point numbers. The default for Cis double.

This is a reasonable choice when you consider the official format for highly precise floating point numbers is double:

OpenAPI has two numeric types, number and integer, where number includes both integer and floating-point numbers. An optional format keyword serves as a hint for the tools to use a specific numeric type:

float - Floating-point numbers. double - Floating-point numbers with double precision.

Let's tweak our pet definition to reflect this:

With this in place, when we dotnet run we create a class that looks like this:

Cdevelopers may well rather work with a decimal type which can handle "financial calculations that require large numbers of significant integral and fractional digits and no round-off errors".

There is a way to switch from using double to decimal in your generated clients. I've been using the approach for some years, and I suspect I first adapted it from a comment on GitHub.

It uses the visitor pattern and looks like this:

The code above, when invoked upon our OpenApiDocument, changes the format of all number types to be decimal. Which results in code along these lines:

If we take all the code, and put it together, we end up with this:

Conclusion

This post takes the tremendous NSwag, and demonstrates a mechanism for using it to create Cclients from an Open API / Swagger documents which:

  • can handle property names with an @ prefix which might collide with the same property without the prefix
  • use decimal as the preferred number type for floating point numbers

Discussion in the ATmosphere

Loading comments...