0

I wrote a custom converter for a JSON object that accepts different types of values in a .NET 6 web API. I registered this converter in my Program.cs class like this:

builder.Services.AddControllers(opt => opt.Conventions.Add(new RouteTokenTransformerConvention(new SlugifyParameterTransformer())))
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
        options.JsonSerializerOptions.Converters.Add(new MyConverter());
        options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
        options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
        options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
    });

If I call the Serialize method like this:

public override void Write(Utf8JsonWriter writer, Quota value, JsonSerializerOptions options)
{
    JsonSerializer.Serialize(writer, value, options);
}

Then it throws a StackOverflowException, I guess because the options parameter contains a reference to MyConverter. If I create a new JsonSerializerOptions object with everything that is in my Program.cs class except for the converter registration, then it works fine, but it breaks the DRY principle.

How could I avoid this?

Edit: the stack trace looks like this, repeating many times:

 at System.Text.Json.Serialization.JsonConverter`1[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TryWrite(System.Text.Json.Utf8JsonWriter, System.__Canon ByRef, System.Text.Json.JsonSerializerOptions, System.Text.Json.WriteStack ByRef)
   at System.Text.Json.Serialization.JsonConverter`1[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].WriteCore(System.Text.Json.Utf8JsonWriter, System.__Canon ByRef, System.Text.Json.JsonSerializerOptions, System.Text.Json.WriteStack ByRef)
   at System.Text.Json.JsonSerializer.WriteUsingSerializer[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Text.Json.Utf8JsonWriter, System.__Canon ByRef, System.Text.Json.Serialization.Metadata.JsonTypeInfo)
   at System.Text.Json.JsonSerializer.Serialize[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Text.Json.Utf8JsonWriter, System.__Canon, System.Text.Json.JsonSerializerOptions)
   at MyProject.Infrastructure.Shared.MyConverter.Write(System.Text.Json.Utf8JsonWriter, MyProject.Domain.Shared.Quota, System.Text.Json.JsonSerializerOptions)
   at System.Text.Json.Serialization.JsonConverter`1[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TryWrite(System.Text.Json.Utf8JsonWriter, System.__Canon ByRef, System.Text.Json.JsonSerializerOptions, System.Text.Json.WriteStack ByRef)
   at System.Text.Json.Serialization.Converters.IEnumerableDefaultConverter`2[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnWriteResume(System.Text.Json.Utf8JsonWriter, System.__Canon, System.Text.Json.JsonSerializerOptions, System.Text.Json.WriteStack ByRef)
   at System.Text.Json.Serialization.JsonCollectionConverter`2[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnTryWrite(System.Text.Json.Utf8JsonWriter, System.__Canon, System.Text.Json.JsonSerializerOptions, System.Text.Json.WriteStack ByRef)
   at System.Text.Json.Serialization.JsonConverter`1[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TryWrite(System.Text.Json.Utf8JsonWriter, System.__Canon ByRef, System.Text.Json.JsonSerializerOptions, System.Text.Json.WriteStack ByRef)
   at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].GetMemberAndWriteJson(System.Object, System.Text.Json.WriteStack ByRef, System.Text.Json.Utf8JsonWriter)
   at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnTryWrite(System.Text.Json.Utf8JsonWriter, System.__Canon, System.Text.Json.JsonSerializerOptions, System.Text.Json.WriteStack ByRef)
   at System.Text.Json.Serialization.JsonConverter`1[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TryWrite(System.Text.Json.Utf8JsonWriter, System.__Canon ByRef, System.Text.Json.JsonSerializerOptions, System.Text.Json.WriteStack ByRef)
   at System.Text.Json.Serialization.Converters.ListOfTConverter`2[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnWriteResume(System.Text.Json.Utf8JsonWriter, System.__Canon, System.Text.Json.JsonSerializerOptions, System.Text.Json.WriteStack ByRef)
   at System.Text.Json.Serialization.JsonCollectionConverter`2[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnTryWrite(System.Text.Json.Utf8JsonWriter, System.__Canon, System.Text.Json.JsonSerializerOptions, System.Text.Json.WriteStack ByRef)
   at System.Text.Json.Serialization.JsonConverter`1[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TryWrite(System.Text.Json.Utf8JsonWriter, System.__Canon ByRef, System.Text.Json.JsonSerializerOptions, System.Text.Json.WriteStack ByRef)
   at System.Text.Json.Serialization.JsonConverter`1[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].WriteCore(System.Text.Json.Utf8JsonWriter, System.__Canon ByRef, System.Text.Json.JsonSerializerOptions, System.Text.Json.WriteStack ByRef)
   at System.Text.Json.Serialization.JsonConverter`1[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].WriteCoreAsObject(System.Text.Json.Utf8JsonWriter, System.Object, System.Text.Json.JsonSerializerOptions, System.Text.Json.WriteStack ByRef)
   at System.Text.Json.JsonSerializer.WriteCore[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Text.Json.Serialization.JsonConverter, System.Text.Json.Utf8JsonWriter, System.__Canon ByRef, System.Text.Json.JsonSerializerOptions, System.Text.Json.WriteStack ByRef)
   at System.Text.Json.JsonSerializer+<WriteStreamAsync>d__112`1[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[[System.Text.Json.JsonSerializer+<WriteStreamAsync>d__112`1[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], System.Text.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51]](<WriteStreamAsync>d__112`1<System.__Canon> ByRef)

The SlugifyParameterTransformer class:

public sealed class SlugifyParameterTransformer : IOutboundParameterTransformer
{
    public string? TransformOutbound(object? value)
    {
        if (value is not string) { return null; }

        // Slugify value
        return Regex.Replace((value as string)!, "([a-z])([A-Z])", "$1-$2").ToLower();
    }
}
anon37894203
  • 431
  • 4
  • 17
  • "_I guess because the options parameter contains a reference to MyConverter_" - I'm not sure how you came to that conclusion - can you explain your thought-logic there? – Dai May 08 '23 at 06:45
  • I inspected the `options` object and the `Converters` field has indeed a reference to `MyConverter`, and creating another `JsonSerializerOptions` without that works fine. – anon37894203 May 08 '23 at 06:47
  • In ASP.NET (and .NET in general) I've found a lot of Stack-overflow exceptions are caused by people being careless with field vs. property names (e.g., having a public computed property `String Name` that returns a field named `name` except they put "`Name`" instead of `"name"` - so the property getter calls itself in an infinte-loop (and surprisingly, the C# compiler still does not warn you if you do this, yikes). – Dai May 08 '23 at 06:47
  • _"I inspected the options object and the Converters field has indeed a reference to MyConverter"_ - right, but how did you make the leap to assuming _that_ is a direct cause of the stack-overflow? ...haven't you looked at the actual Stack Trace yet? (I mean, **yes**, I agree that it's a _lead_ to an investigation, but it doesn't _explain_ the observed behaviour at all). Anyway,, assuming that the problem is some code in your converter then, my guess is that you have a self-call in a property `get;set;` _in your converter_. Anyway, we can't really help you without the stack-trace (use WinDbg) – Dai May 08 '23 at 06:49
  • ...and you probably should show us the source-code of your `SlugifyParameterTransformer` type, your DTOs, and your `JsonSerializerOptions` configuration - and just to be sure: you aren't mixing Newtonsoft.JSON (aka JSON.NET) with System.Text.Json? – Dai May 08 '23 at 06:53
  • Yes, I edited the question with the requested info. My converter does not have any properties, just the `CanConvert`, `Write` and `Read` methods. I am not mixing System.Text.Json and other libraries. – anon37894203 May 08 '23 at 06:58
  • Please show us the entire definition of `MyProject.Infrastructure.Shared.MyConverter.Write` - I assume that's a method you wrote yourself? Or is it one of those new Roslyn code-gen-for-JSON thingies? If so, then you may have found a spicy bug in ASP.NET - so this'll be interesting! – Dai May 08 '23 at 06:59
  • That's the entire definition, it is just that line of code. – anon37894203 May 08 '23 at 07:00
  • I think I see the problem - (I haven't switched to SystemTextJson myself, so I cannot be certain), but I'm pretty sure you're not meant to call the "entrypoint" ``Serialize` method from within a Converter, because serialization has _already started_, so what your program ends-up-doing is restarting serialization-from-scratch every time it enters into your converter method. – Dai May 08 '23 at 07:01
  • Looks like a duplicate of [How to use default serialization in a custom System.Text.Json JsonConverter?](https://stackoverflow.com/q/65430420/3744182), agree? – dbc May 08 '23 at 16:01
  • Yes, looks like a duplicate of that question. Apologies, I searched for it and didn't find anything. Removing the converter and passing the new list of converters as parameter worked well. I marked it as duplicated, thank you! – anon37894203 May 29 '23 at 09:16

0 Answers0