How to fix NSwag API client unknown enum value error

Filip Kovář
5 min readJul 12, 2024

--

If you are consuming an API that uses enums as strings and get the NSwag exception “StringEnumConverter — Error converting value “{Value}” to type ‘{Type}’” from your C# generated API client, then you are at the right place. There is one of the possible solutions, or, if you prefer, a workaround.

API Enum Preferences

There are multiple ways on how to expose enum values from your API. The first can be as integer as this.

integer enum representation

And another can be as string as in this case.

string enum representation

This story is about a string enum that I personally prefer. But there is a really annoying issue when you are using NSwag for generating API clients and someone adds a new enum value on the API side. If you have the same trouble, then let’s find the solution.

The problem

The problem occurs when you generate C# API client and after that is deployed a new API version with a new enum value. When the new value is returned from the API to the current API Client, an exception is thrown.

Let’s say you have the following flow:

  • Day 1: API has FriendlyStatus enum with values ‘Good’ and ‘Bad’
  • Day 1: You generate an API client
  • Day 2: A new enum value ‘Best’ is added to the API
  • Day 2: API client calls API
  • Day 2: API returns object with the new enum value ‘Best’
  • Day 2: NSwag API generated client throws an exception: Error converting value “Best” to type FriendlyStatus’

The cause of the problem is in the generated C# code. Specifically, in the decoration of the enum property by attribute

[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]

StringEnumConverter doesn’t support unknown enum values, and what is worse is that NSwag doesn’t allow you to customize or replace converters. That means there is no out-of-the-box solution for how to configure it. Correct me, please, if I’m wrong.

One of the possible solutions or workarounds

As I mentioned earlier, there is no out-of-the-box configuration option to replace the default StringEnumConverter, and thus we have to find another solution. There will probably be more than one solution. For example, on GitHub, there is an issue from 2020 with the advice to replace the Class.liquid template for the generator. I’m not sure if it works.

Let’s check my solution or may be better to say my workaround.

There are two steps:

  • Create your own enum converter. I created StringMissingEnumConverter and inherited it from the default StringEnumConverter
  • That’s the magic trickAdd Pre-build event to replace text from’Newtonsoft.Json.Converters.StringEnumConverter’ to your own enum converter ‘….StringMissingEnumConverter’. I used a simple powershell script. This step is workaround for the missing enum converter configuration.

And here are previous steps (and an extra one) in more detail

  • Create your own enum converter
public class StringMissingEnumConverter : StringEnumConverter
{
private int _newValue = 100000;
private static readonly IDictionary<Type, IDictionary<string, object>> _fromValueDictionary = new Dictionary<Type, IDictionary<string, object>>();
private static readonly IDictionary<Type, IDictionary<object, string>> _toValueDictionary = new Dictionary<Type, IDictionary<object, string>>();
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
var currentValue = Convert.ToString(reader.Value);
var currentObjectType = GetNullableGetUnderlyingType(objectType);
if (currentValue == null || Enum.IsDefined(currentObjectType, currentValue))
{
return base.ReadJson(reader, objectType, existingValue, serializer);
}

IDictionary<string, object>? fromValueDictionary;
IDictionary<object, string>? toValueDictionary = null;
if (!_fromValueDictionary.TryGetValue(currentObjectType, out fromValueDictionary))
{
fromValueDictionary = new Dictionary<string, object>();
_fromValueDictionary.Add(currentObjectType, fromValueDictionary);

toValueDictionary = new Dictionary<object, string>();
_toValueDictionary.Add(currentObjectType, toValueDictionary);
}

if (!_toValueDictionary.TryGetValue(currentObjectType, out toValueDictionary))
{
throw new InvalidOperationException("Failed to initialize dictionary");
}

object? fromValue;
if (!fromValueDictionary.TryGetValue(currentValue, out fromValue))
{
fromValue = Enum.ToObject(currentObjectType, _newValue++);
fromValueDictionary.Add(currentValue, fromValue);
toValueDictionary.Add(fromValue, currentValue);
}

return fromValue;
}

private Type GetNullableGetUnderlyingType(Type type)
{
return Nullable.GetUnderlyingType(type) ?? type;
}

public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value != null)
{
var enumType = GetNullableGetUnderlyingType(value.GetType());

if (!Enum.IsDefined(enumType, value))
{
value = _toValueDictionary[enumType][Enum.ToObject(enumType, value)];
writer.WriteValue(value);
return;
}
}

base.WriteJson(writer, value, serializer);
}
}
  • Create powershell script to replace converters in the C# source “text”. For example ApiClientAdjustment.ps1
$apiClientPath=$args[0]
(Get-Content $apiClientPath).Replace('Newtonsoft.Json.Converters.StringEnumConverter', 'ConsoleApp1.StringMissingEnumConverter') | Set-Content $apiClientPath
  • And finally add Pre-build event to your project
  <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="Powershell.exe -ExecutionPolicy Bypass -NoProfile -NonInteractive -Command &quot;$(ProjectDir)\ApiClientAdjustment.ps1&quot; '$(ProjectDir)\Connected Services\SalesApiClient\OpenAPI.cs'" />
</Target>
Project properties

Rough description of the source code

This code defines a custom JSON converter class called StringMissingEnumConverter that extends StringEnumConverter. Here's a rough description of its functionality:

  1. It’s designed to handle enum serialization and deserialization, particularly for cases where the enum value might not be defined.
  2. The class maintains two dictionaries to map between string representations and enum values:
  • _fromValueDictionary: Maps strings to enum values
  • _toValueDictionary: Maps enum values to strings

3. The ReadJson method:

  • Attempts to read enum values from JSON
  • If the value is defined in the enum, it uses the base class behavior
  • If not, it creates a new enum value and stores the mapping

4. The WriteJson method:

  • Handles writing enum values to JSON
  • For undefined enum values, it writes the string representation instead

5. The class uses a counter (_newValue) to assign values to undefined enum entries, starting from 100000.

6. It includes a helper method GetNullableGetUnderlyingType for handling nullable types.

Notes

  • All adjustments are in the project that contains your generated API Client
  • This is just example code and it’s not suitable for production without review
  • StringMissingEnumConverter is not thread-safe and should be improved e.g. use ConcurrentDictionary instead of Dictionary, use Interlocked.Increment to incementing _newValue and so on
  • There can be other scenarios that are not covered by this example

Conclusion

In this story, we fixed a situation where adding a new value to the enum on the API side break generated an API C# client. There can be better solutions, but I was probably not lucky enough to find them. If you have a better solution, then please let me know in the comment below.

Please, subscribe my YouTube channel for more content like this

--

--

Filip Kovář
Filip Kovář

Written by Filip Kovář

Software architect , technology enthusiast, Musician, Content creator

No responses yet